共计 16849 个字符,预计需要花费 43 分钟才能阅读完成。
一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
三 DOM 罕用 API |
四 null 和 undefined 的区别 |
五 事件流 |
5.1 addEventListener |
5.2 原理 |
5.3 案例 |
5.4 练习题 |
5.5 阻止冒泡 |
5.6 onmouseover 和 onmouseenter 区别 |
5.7 科普 |
六 typeof 和 instanceof 的区别 |
七 一句话形容 this |
八 JS 地位 |
九 JS 拖拽 |
十 setTimeout 实现 setInterval |
十一 实现 Sleep |
十二 执行上下文 |
12.1 执行上下文类型 |
12.2 执行栈 |
十三 函数式编程 |
13.1 函数式编程特点 |
13.2 纯函数 |
十四 渐进式网络应用(PWA) |
14.1 长处 |
14.2 毛病 |
十五 规范化 |
15.1 CommonJS 标准 |
15.2 AMD 标准 |
15.3 CMD 标准 |
15.4 ES6 Modules 标准 |
十六 babel 编译原理 |
十七 题集 |
17.1 数组常见 API |
17.2 常见 DOM API |
17.3 数组去重 |
17.4 数字化金额 |
17.5 遍历问题 |
17.6 setTimeout |
17.7 requestAnimationFrame |
17.8 暂时性死区 |
17.9 输入打印后果 |
17.10 输入打印后果 |
17.11 Event Loop |
17.12 输入打印后果 |
17.13 使 a == 1 && a == 2 成立 |
十八 More |
二 前言
返回目录
在 JavaScript 温习过程中,可能会碰到:
null
和undefined
的区别?addEventListener
函数?
这样杂七杂八的问题,亦或者 a == 1 && a == 2
这样乏味的问题。
将它们归类到 JavaScript 根底,并在本篇文章中一一讲述。
同时,会有十几道简略题目练手。
三 DOM 罕用 API
返回目录
能够应用 document
或 window
元素的 API 来操作文档自身或获取文档的子类(Web 页面中的各种元素)。
// 获取元素
const node = document.getElementById(id); // 或者 querySelector(".class|#id|name");
// 创立元素
const heading = document.createElement(name); // name: p、div、h1...
heading.innerHTML = '';
// 增加元素
document.body.appendChild(heading);
// 删除元素
document.body.removeChild(node);
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>DOM 操作 </title>
<style>
div {
border: 1px solid #ccc;
padding: 50px;
width: 100px;
}
</style>
</head>
<body>
<div id="dom1"> 元素 1</div>
<div class="dom2"> 元素 2</div>
<button class="btn"> 点我 </button>
<script>
(function() {const btn = document.querySelector('.btn');
// 注册点击事件
btn.onclick = function() {const dom1 = document.getElementById('dom1');
// 第一种增加元素
const newDom1 = document.createElement('p');
newDom1.innerHTML = '<a href="https://github.com/LiangJunrong/document-library">jsliang 的文档库 </a>';
dom1.appendChild(newDom1);
// 第二种增加元素
const newDom2 = document.createElement('ul');
newDom2.innerHTML = `
<li>aaa</li>
<li>bbb</li>
`;
document.body.appendChild(newDom2);
// 移除元素
const dom2 = document.querySelector('.dom2');
document.body.removeChild(dom2);
}
})()
</script>
</body>
</html>
四 null 和 undefined 的区别
返回目录
null
示意无
的对象,也就是此处不应该有值;而undefined
示意未定义。- 在转换数字的时候,
Number(null)
为0
,而Number(undefined)
为NaN
。
应用场景细分如下:
null
:
- 作为函数的参数,示意该函数的参数不是对象。
- 作为对象原型链的起点。
Object.prototype.__proto__ === null
undefined
:
- 变量被申明然而没有赋值,等于
undefined
。 - 调用函数时,对应的参数没有提供,也是
undefined
。 - 对象没有赋值,这个属性的值为
undefined
。 - 函数没有返回值,默认返回
undefined
。
五 事件流
返回目录
什么是事件流:事件流形容的是从页面中接管事件的程序,DOM 2
级事件流包含上面几个阶段。
- 事件捕捉阶段
- 处于指标阶段
- 事件冒泡阶段
如何让事件先冒泡后捕捉:
在 DOM
规范事件模型中,是先捕捉后冒泡。然而如果要实现先冒泡后捕捉的成果,对于同一个事件,监听捕捉和冒泡,别离对应相应的处理函数,监听到捕捉事件,先暂缓执行,直到冒泡事件被捕捉后再执行捕捉之间。
5.1 addEventListener
返回目录
addEventListener
办法将指定的监听器注册到 EventTarget
上,当该对象触发指定的事件时,指定的回调函数就会被执行。
addEventListener
事件指标能够是文档上的元素 Element
、Document
和 Window
或者任何其余反对事件的对象(例如 XMLHttpRequest
)。
参考文档:EventTarget.addEventListener – MDN
- 语法:
target.addEventListener(type, listener, options/useCapture)
type
:示意监听事件类型的字符串listener
:所监听的事件触发,会承受一个事件告诉对象。options
:一个指定无关listener
属性的可选参数对象。可选值有capture
(事件捕捉阶段流传到这里触发)、once
(在listener
增加之后最多值调用一次)、passive
(设置为true
时示意listener
永远不会调用preventDefault()
)。useCapture
:在 DOM 树中,注册了listener
的元素,是否要先于它上面的EventTarget
调用该listener
。
addEventListener
的第三个参数波及到冒泡和捕捉,为true
时是捕捉,为false
时是冒泡。或者是一个对象
{passive: true}
,针对的是Safari
浏览器,禁止 / 开启应用滚动的时候要用到
- 示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title> 监听器 </title>
</head>
<body>
<table id="outside">
<tr><td id="t1">one</td></tr>
<tr><td id="t2">two</td></tr>
</table>
<script>
(function() {
// 增加函数
const modifyText = (text) => {const t2 = document.querySelector('#t2');
if (t2.firstChild.nodeValue === text) {t2.firstChild.nodeValue = 'two';} else {t2.firstChild.nodeValue = text;}
}
// 给 Table 增加事件监听器
const element = document.querySelector('#outside');
element.addEventListener('click', function() {modifyText('four') }, false);
})()
</script>
</body>
</html>
如上,这个示例简略实现了点击 two
切换到 four
,点击 four
再切换到 two
的成果。
5.2 原理
返回目录
事件捕捉和事件冒泡别离是 网景(Netscape)和 IE 对 DOM
事件产生程序的形容。
网景 认为 DOM
接管的事件应该最先是 window
,而后到 document
,接着一层一层往下,最初才到具体的元素接管到事件,即 事件捕捉。
IE 则认为 DOM
事件应该是具体元素先接管到,而后再一层一层往上,接着到 document
,最初才到 window
,即 事件冒泡。
最初 W3C 对这两种计划进行了对立:将 DOM
事件分为两个阶段,事件捕捉和事件冒泡阶段。
当一个元素被点击,首先是事件捕捉阶段,window
最先接管事件,而后一层一层往下捕捉,最初由具体元素接管;之后再由具体元素再一层一层往上冒泡,到 window
接管事件。
所以:
- 事件冒泡:当给某个指标元素绑定了事件之后,这个事件会顺次在它的父级元素中被触发(当然前提是这个父级元素也有这个同名称的事件,比方子元素和父元素都绑定了
click
事件就触发父元素的click
)。 - 事件捕捉:和冒泡相同,会从下层传递到上层。
5.3 案例
返回目录
联合自定义事件耍个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title> 自定义事件 </title>
</head>
<body>
<ul class="ul">
<li class="li">
<button class="btn"> 点我 </button>
</li>
</ul>
<script>
window.onload = function() {const myEvent = document.createEvent('CustomEvent');
myEvent.initEvent('myEvent', true, true);
const btn = document.querySelector('.btn');
btn.addEventListener('myEvent', function(e) {console.log('button');
})
const li = document.querySelector('.li');
li.addEventListener('myEvent', (e) => {console.log('li');
})
const ul = document.querySelector('.ul');
li.addEventListener('myEvent', (e) => {console.log('ul');
})
document.addEventListener('myEvent', (e) => {console.log('document');
})
window.addEventListener('myEvent', (e) => {console.log('window');
})
setTimeout(() => {btn.dispatchEvent(myEvent);
}, 2000);
};
</script>
</body>
</html>
Chrome 输入下程序是:button
-> li
-> ul
-> document
-> window
如果是捕捉的话,那么则相同。
5.4 练习题
返回目录
点击一个 input
顺次触发的事件
const text = document.getElementById('text');
text.onclick = function (e) {console.log('onclick')
}
text.onfocus = function (e) {console.log('onfocus')
}
text.onmousedown = function (e) {console.log('onmousedown')
}
text.onmouseenter = function (e) {console.log('onmouseenter')
}
正确程序是:onmouseenter -> onmousedown -> onfocus -> onclick
。
如果加上 onmouseup
,那就是:
onmouseenter -> onmousedown -> onfocus -> onmouseup -> onclick
5.5 阻止冒泡
返回目录
event.stopPropagation();
btn.addEventListener('myEvent', function(e) {console.log('button');
event.stopPropagation();})
通过阻止冒泡,程序只会输入 button
,而不会持续输入 li
等。
5.6 onmouseover 和 onmouseenter 区别
返回目录
这两者都是移入的时候触发,然而 onmouseover
会触发屡次,而 onmouseenter
只在进去的时候才触发。
5.7 科普
返回目录
并不是所有的事件都有冒泡,例如:
onblur
onfocus
onmouseenter
onmouseleave
六 typeof 和 instanceof 的区别
返回目录
typeof
:对某个变量类型的检测,根本类型除了null
之外,都能失常地显示为对应的类型,援用类型除了函数会显示为function
,其余都显示为object
。instanceof
次要用于检测某个构造函数的原型对象在不在某个对象的原型链上。
typeof
会对 null
显示谬误是个历史 Bug,typeof null
输入的是 object
,因为 JavaScript 早起版本是 32 位零碎,为了性能思考应用低位存储变量的类型信息,000
结尾代表是对象然而 null
示意为全零,所以它错误判断为 object
。
另外还有 Object.prototype.toString.call()
进行变量判断。
具体可见:JavaScript – 变量
七 一句话形容 this
返回目录
对于函数而言,指向最初调用函数的那个对象,是函数运行时外部主动生成的一个外部对象,只能在函数外部应用;对于全局而言,this
指向 window
。
八 JS 地位
返回目录
clientHeight
:示意可视区域的高度,不蕴含border
和滚动条offsetHeight
:示意可视区域的高度,蕴含了border
和滚动条scrollHeight
:示意了所有区域的高度,蕴含了因为滚动被暗藏的局部clientTop
:示意边框border
的厚度,在未指定的状况下个别为0
scrollTop
:滚动后被暗藏的高度,获取对象绝对于由offsetParent
属性指定的父坐标(CSS 定位的元素或body
元素)间隔顶端的高度。
九 JS 拖拽
返回目录
- 通过
mousedown
、mousemove
、mouseup
办法实现 - 通过 HTML5 的
Drag
和Drop
实现
十 setTimeout 实现 setInterval
返回目录
这算另类知识点吧,原本打算归类手写源码系列的,然而想想太 low
了,没牌面,入根底系列吧:
const say = () => {
// do something
setTimeout(say, 200);
};
setTimeout(say, 200);
革除这个定时器:
let i = 0;
const timeList = [];
const say = () => {
// do something
console.log(i++);
timeList.push(setTimeout(say, 200));
};
setTimeout(say, 200);
setTimeout(() => {for (let i = 0; i < timeList.length; i++) {clearTimeout(timeList[i]);
}
}, 1000);
十一 实现 Sleep
返回目录
如下,实现 1000
毫秒后执行其余内容:
const sleep = time => {return new Promise((resolve, reject) => {setTimeout(() => {resolve(time);
}, time);
});
};
sleep(1000).then((res) => {console.log(res);
});
十二 执行上下文
返回目录
12.1 执行上下文类型
返回目录
JavaScript 中有 3 种执行上下文类型:
- 全局执行上下文:这是默认或者说根底的上下文,任何不在函数外部的代码都在全局上下文中。它会执行两件事:创立一个全局的
window
对象(浏览器的状况下),并且设置this
的值等于这个全局对象。一个程序中只会有一个全局执行上下文。 - 函数执行上下文:每当一个函数被调用时, 都会为该函数创立一个新的上下文。每个函数都有它本人的执行上下文,不过是在函数被调用时创立的。函数上下文能够有任意多个。每当一个新的执行上下文被创立,它会按定义的程序执行一系列步骤。
- Eval 函数执行上下文:执行在
eval
函数外部的代码也会有它属于本人的执行上下文,但因为 JavaScript 开发者并不常常应用eval
,所以在这里我不会探讨它。
12.2 执行栈
返回目录
执行栈,也就是在其它编程语言中所说的“调用栈”,是一种领有 LIFO
(后进先出)数据结构的栈,被用来存储代码运行时创立的所有执行上下文。
当 JavaScript 引擎第一次遇到你的脚本时,它会创立一个全局的执行上下文并且压入以后执行栈。每当引擎遇到一个函数调用,它会为该函数创立一个新的执行上下文并压入栈的顶部。
引擎会执行那些执行上下文位于栈顶的函数。当该函数执行完结时,执行上下文从栈中弹出,管制流程达到以后栈中的下一个上下文。
let a = 'Hello World!';
function first() {console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
十三 函数式编程
返回目录
函数式编程(Functional Programming,简称 FP)。
函数式编程:通过对面向对象式编程代码的拆分,将各个性能独立进去,从而达到性能独立、易复用等目标。
举例:代码转换
['john-reese', 'harold-finch', 'sameen-shaw']
// 转换成
[{name: 'John Reese'}, {name: 'Harold Finch'}, {name: 'Sameen Shaw'}]
对下面代码进行转换。
const arr = ['john-reese', 'harold-finch', 'sameen-shaw'];
const newArr = [];
for (let i = 0, len = arr.length; i < len ; i++) {let name = arr[i];
let names = name.split('-');
let newName = [];
for (let j = 0, naemLen = names.length; j < naemLen; j++) {let nameItem = names[j][0].toUpperCase() + names[j].slice(1);
newName.push(nameItem);
}
newArr.push({name : newName.join(' ') });
}
return newArr;
这份代码中,有 2 个局部:
- 拆分数组中字符串,将字符串变成人名。
john-reese -> John Reese
- 将数组转换成对象。
['John Reese'] -> [{name: 'John Reese'}]
所以咱们间接能够改变:
/**
* @name 扭转人名展现形式
* @param {array} arr 须要扭转的数组
* @param {string} type 反对不同格局的人名
*/
const changeName = (arr, type) => {return arr.map(item => item.split(type).map(name => name[0].toUpperCase() + name.slice(1)).join(' '));
};
/**
* @name 数组扭转成对象
* @param {array} arr 须要扭转的数组
* @param {string} key 对应变成什么字段
* @return {object} 返回扭转后的对象
*/
const arrToObj = (arr, key) => {return arr.map(item => ({ [key]: item }));
};
const result = arrToObj(changeName(['john-reese', 'harold-finch', 'sameen-shaw'], '-'), 'name');
console.log(result); // [{ name: 'John Reese'}, {name: 'Harold Finch'}, {name: 'Sameen Shaw'} ]
嗨,这不就是对性能封装吗?一般来说工作中呈现 2 次以上的代码才进行封装。
函数式编程就是对能够抽离的性能都进行抽取封装。
到这里好像把握了真谛,jsliang 也没具体理解定义撒,心愿没误导。
13.1 函数式编程特点
返回目录
- 函数是一等公民。能够利用这点让它反对抽取到内部。
- 申明做某件工夫。函数式编程大多数申明某个函数须要做什么,而不是它怎么做的。
- 便于垃圾回收。函数外部的变量不便垃圾回收,不会产生太多的变量,用户不须要大量的定义。
- 数据不可变。函数式编程要求所有的数据都是不可变的,如果须要批改某个对象,应该新建后再批改,而不是净化本来的数据。
- 无状态。不论什么时候运行,同一个函数对雷同的输出返回雷同的输入,而不依赖内部状态的变动。
- 无副作用。性能 A 应该仅仅为了实现它的实现,而不会随着内部的扭转而扭转,这样当它执行结束之后,就能够将其外部数据进行回收。并且它不会批改传入的参数。
重视援用值(Object、Array)的传递,尽可能不要净化传入的数据。
13.2 纯函数
返回目录
纯函数的概念有 2 点:
- 不依赖内部状态(无状态):函数的运行后果不依赖全局变量,
this
指针,IO
操作等。 - 没有副作用(数据不变):不批改全局变量,不批改入参。
长处:
- 便于测试和优化
- 可缓存性
- 自文档化
- 更少 Bug
十四 渐进式网络应用(PWA)
返回目录
渐进式网络应用(PWA)是谷歌在 2015 年底提出的概念。基本上算是 Web 应用程序,但在外观和感觉上与原生 App 相似。反对 PWA 的网站能够提供脱机工作、推送告诉和设施硬件拜访等性能。
14.1 长处
返回目录
- 更小更快: 渐进式的 Web 应用程序比原生应用程序小得多。他们甚至不须要装置。这是他们没有节约磁盘空间和加载速度十分快。
- 响应式界面: PWA 反对的网页可能主动适应各种屏幕大小。它能够是手机、平板、台式机或笔记本。
- 无需更新: 大多数挪动应用程序须要每周定期更新。与一般网站一样,每当用户交互产生且不须要应用程序或游戏商店批准时,PWA 总是加载最新更新版本。
- 高性价比:原生挪动利用须要别离为 Android 和 iOS 设施开发,开发成本十分高。另一方面,PWA 有着雷同的性能,但只是先前价格的一小部分,开发成本低。
- SEO 劣势:搜索引擎能够发现 PWA,并且加载速度十分快。就像其余网站一样,它们的链接也能够共享。提供良好的用户体验和后果,在 SEO 排名进步。
- 脱机性能:因为 Service Worker API 的反对,能够在脱机或低 internet 连贯中拜访 PWAs。
- 安全性:PWA 通过 HTTPS 连贯传递,并在每次交互中爱护用户数据。
- 推送告诉:通过推送告诉的反对,PWA 轻松地与用户进行交互,提供十分棒的用户体验。
- 绕过利用商店:原生 App 如果须要任何新的更新,须要利用商店几天的审批,且有被回绝或禁止的可能性,对于这方面来说,PWA 有它独特的劣势,不须要 App Store 反对。更新版本能够间接从 Web 服务器加载,无需 App Store 批准。
- 零装置:在浏览过程中,PWA 会在手机和平板电脑上有本人的图标,就像挪动应用程序一样,但不须要通过简短的装置过程。
14.2 毛病
返回目录
- 对系统性能的拜访权限较低:目前 PWA 对本机系统性能的拜访权限比原生 App 无限。而且,所有的浏览器都不反对它的全副性能,但可能在不久的未来,它将成为新的开发规范。
- 少数 Android,多数 iOS:目前更多的反对来自 Android。iOS 零碎只提供了局部。
- 没有审查规范:PWA 不须要任何实用于利用商店中本机利用的审查,这可能会加快进程,但不足从应用程序商店中获取推广效益。
十五 规范化
返回目录
CommonJS
标准、AMD
标准、CMD
标准、ES6 Modules
标准这 4 者都是前端规范化的内容,那么它们之间区别是啥呢?
在没有这些之前,咱们通过:
- 一个函数就是一个模块。
function fn() {}
- 一个对象就是一个模块。
let obj = new Object({...})
- 立刻执行函数(IIFE)。
(function() {})()
15.1 CommonJS 标准
返回目录
这之后,就有了 CommonJS
标准,其实 CommonJS
咱们见得不少,就是 Node
的那套:
- 导出:
module.exports = {}
、exports.xxx = 'xxx'
- 导入:
require(./index.js)
- 查找形式:查找当前目录是否具备文件,没有则查找当前目录的
node_modules
文件。再没有,冒泡查问,始终往零碎中的npm
目录查找。
它的特点:
- 所有代码在模块作用域内运行,不会净化其余文件
require
失去的值是值的拷贝,即你援用其余 JS 文件的变量,批改操作了也不会影响其余文件
它也有本人的缺点:
- 利用层面。在
index.html
中做var index = require('./index.js')
操作报错,因为它最终是后盾执行的,只能是index.js
援用index2.js
这种形式。 - 同步加载问题。
CommonJS
标准中模块是同步加载的,即在index.js
中加载index2.js
,如果index2.js
卡住了,那就要等很久。
15.2 AMD 标准
返回目录
为什么有 AMD
标准?
答:CommonJS
标准不中用:
- 实用客户端
- 期待加载(同步加载问题)。
所以它做了啥?
能够采纳异步形式加载模块。AMD
是 Asynchronous Module Definition
的缩写,也就是“异步模块定义”,记住这个 async
就晓得它是异步的了。
15.3 CMD 标准
返回目录
CMD (Common Module Definition), 是 seajs 推崇的标准,CMD 则是依赖就近,用的时候再 require
。
AMD 和 CMD 最大的区别是对依赖模块的执行机会解决不同,留神不是加载的机会或者形式不同,二者皆为异步加载模块。
15.4 ES6 Modules 标准
返回目录
- 导出:
export a
export {a}
export {a as jsliang}
export default function() {}
- 导入:
import './index'
import {a} from './index.js'
import {a as jsliang} from './index.js'
import * as index from './index.js'
特点:
export
命令和import
命令能够呈现在模块的任何地位,只有处于模块顶层就能够。如果处于块级作用域内,就会报错,这是因为处于条件代码块之中,就没法做动态优化了,违反了 ES6 模块的设计初衷。import
命令具备晋升成果,会晋升到整个模块的头部,首先执行。
和 CommonJS
区别:
CommonJS
模块是运行时加载,ES6 Modules
是编译时输入接口CommonJS
输入是值的拷贝;ES6 Modules
输入的是值的援用,被输出模块的外部的扭转会影响援用的扭转CommonJs
导入的模块门路能够是一个表达式,因为它应用的是require()
办法;而ES6 Modules
只能是字符串CommonJS this
指向以后模块,ES6 Modules
的this
指向undefined
ES6 Modules
中没有这些顶层变量:arguments
、require
、module
、exports
、__filename
、__dirname
十六 babel 编译原理
返回目录
babylon
将ES6/ES7
代码解析成AST
babel-traverse
对AST
进行遍历转译,失去新的AST
- 新
AST
通过babel-generator
转换成ES5
这一块的话 jsliang 并没有过分深究,单纯了解的话还是容易了解的:
- 黑白七巧板组成的形态,拆分进去失去整机(
ES6/ES7
解析成AST
) - 将这些整机换成黑白的(
AST
编译失去新AST
) - 将黑白整机拼装成新的形态(
AST
转换为ES5
)
十七 题集
返回目录
17.1 数组常见 API
返回目录
push
:数组尾部增加元素unshift
:数组头部增加元素pop
:数组尾部移除元素shift
:数组头部移除元素splice
:删除数组元素slice
:截取数组元素indexOf
:查找某元素第一次呈现的地位lastIndexof
:查找某元素最初一次呈现的地位findIndex
:查找元素第一次呈现的地位forEach
:遍历元素map
:遍历元素filter
:过滤元素some
:蕴含某元素every
:所有元素和某元素统一includes
:查看是否蕴含某元素concat
:合并元素join
:合并元素,变成字符串toString
:变成字符串sort
:元素排序
17.2 常见 DOM API
返回目录
- 获取
- 创立
- 增加
- 删除
// 获取元素
const node = document.getElementById(id); // 或者 querySelector(".class|#id|name");
// 创立元素
const heading = document.createElement(name); // name: p、div、h1...
heading.innerHTML = '';
// 增加元素
document.body.appendChild(heading);
// 删除元素
document.body.removeChild(node);
17.3 数组去重
返回目录
数组去重是个常常提及的点:
const arr = [1, 1, 2, 3, 3];
// 冀望失去:[1, 2, 3]
// 办法一:for 配合新数组截取
const newArr1 = [];
for (let i = 0; i < arr.length; i++) {if (!newArr1.includes(arr[i])) {newArr1.push(arr[i]);
}
}
console.log('newArr1:', newArr1);
// 办法二:应用 Set
const newArr2 = [...new Set(arr)];
console.log('newArr2:', newArr2);
// 办法三:应用 filter
const newArr3 = arr.filter((item, index) => arr.lastIndexOf(item) === index);
console.log('newArr3:', newArr3);
有一次面试碰到的有意思的发问是:不应用数组 API
进行去重。
留神:不能应用
push
、indexOf
等API
17.4 数字化金额
返回目录
- 办法一:暴力遍历
const num = String(1234567890);
let result = '';
for (let i = num.length - 1; i >= 0; i--) {if (i !== num.length - 1 && i % 3 === 0) {result = num[i] + ',' + result;
} else {result = num[i] + result;
}
}
console.log(result);
- 办法二:API 技巧
console.log(String(1234567890).split('').reverse().reduce((prev, next, index) => (index % 3) === 0 ? next +',' + prev : next + prev)
);
- 办法三:API 技巧
console.log((1234567890).toLocaleString('en-US')
);
- 办法四:正则表达式
String(1234567890).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
17.5 遍历问题
返回目录
以下代码执行后,array 的后果是?
let array = [, 1, , 2, , 3];
array = array.map((i) => ++i)
- A:
[, 2, , 3, , 4]
- B:
[NaN, 2, NaN, 3, NaN, 4]
- C:
[1, 2, 1, 3, 1, 4]
- D:
[null, 2, null, 3, null, 4]
答案:A
解释:
forEach()
、filter()
、reduce()
、every()
和some()
都会跳过空位。map()
会跳过空位,但会保留这个值join()
和toString()
会将空位视为undefined
,而undefined
和null
会被解决成空字符串。
17.6 setTimeout
返回目录
for (var i = 0; i < 5; i++) {setTimeout(() => {console.log(i);
}, 1000);
}
以上代码执行后果?
- A:5 5 5 5 5
- B:0 0 0 0 0
- C:0 1 2 3 4
- D:1 2 3 4 5
答案:A
解析:
var i
在for
中应用,会造成变量净化,从而导致全局有一个遍历i
,这个i
运行到最初,就是5
setTimeout
是宏工作,在script
这个宏工作执行结束后才执行,所以收集到的i
是5
- 最终输入 5 个
5
17.7 requestAnimationFrame
返回目录
for (let i = 0; i < 5; i++) {requestAnimationFrame(() => {console.log(i);
});
}
以上代码执行后果:
- A:1 2 3 4 5
- B:0 1 2 3 4
- C:4 4 4 4 4
- D:5 5 5 5 5
答案:B
解析:
let i
使for
造成块级作用域。requestAnimationFrame
相似于setTimeout
,然而它能够当成一个微工作来看,是在微工作队列执行结束后,执行 UI 渲染前,调用的一个办法。- 因而,这道题并不是指
requestAnimationFrame
会收集i
,而是let
造成了块级作用域的问题,如果改成var i
,照样输入 5 个5
。
17.8 暂时性死区
返回目录
1、上面代码输入什么?
let a = 1;
let test = function() {console.log(a);
a++;
}
test();
2、上面代码输入什么?
let a = 1;
let test = function() {console.log(a);
let a = 2;
a++;
}
test();
答案:
第一道题输入:1
第二道题输入:Uncaught ReferenceError: Cannot access 'a' before initialization
解析:
其起因是在同一个 block
中,let
在前面从新定义的,那么就不能在之前援用该变量。同时,也不能取嵌套外层的值。
17.9 输入打印后果
返回目录
function sayHi() {console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
下面代码输入后果?
答案:undefined、报错
解析:
这道题转变一下就看明确了:
function sayHi() {
var name; // 变量晋升 - 变量申明
console.log(name); // undefined
console.log(age); // let 存在暂时性死区,不会变量晋升
name = "Lydia"; // 变量晋升 - 变量赋值
let age = 21;
}
sayHi();
17.10 输入打印后果
返回目录
function myFunc() {console.log(a);
console.log(func());
var a = 1;
function func() {return 2;}
}
myFunc();
请问输入啥?
答案:undefined
2
解析:不难,不解析了
17.11 Event Loop
返回目录
for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 1);
}
for (let i = 0; i < 3; i++) {setTimeout(() => console.log(i), 1);
}
下面代码输入后果?
答案和解析:
第一道题目:var
在 for
中存在变量净化,同步代码 for
执行结束之后,再执行宏工作 setTimeout
,发现以后 i
都成为 3
了,所以输入 3、3、3
第二道题目:let
在 for
中会造成块级作用域,每次迭代的时候 i
都是一个新值,并且每个值都存在于循环内的块级作用域,所以输入 0、1、2
17.12 输入打印后果
返回目录
let date = new Date();
for (var i = 0; i < 5; i++) {setTimeout(function() {console.log(new Date - date, i); // 1
}, 1000);
}
console.log(new Date - date, i); // 2
请问输入啥?
答案:
0 5
1001 5
1004 5
1005 5
1006 5
1007 5
解析:题目先走宏工作 script
,所以定义了 date
之后,执行正文为 2 这行的 console
。
而后 5 个宏工作,都是定时器 setTimeout
,所以会在之后执行,输入:1000 5
,然而定时器也不肯定准时的,所以有可能是 1001
、1002
或者其余的。
17.13 使 a == 1 && a == 2 成立
返回目录
尝试编码,使:if(a == 1 && a == 2 && a == 3) {}
这种状况成立。
- 办法一
在类型转换的时候,咱们晓得了对象如何转换成原始数据类型。如果部署了 [Symbol.toPrimitive]
,那么返回的就是 Symbol.toPrimitive
的返回值。
当然,咱们也能够把此函数部署在 valueOf
或者是 toString
接口上,成果雷同。
// 利用闭包缩短作用域的个性
let a = {[Symbol.toPrimitive]: (function () {
let i = 1;
return function () {return i++;}
})()}
- 办法二
利用 Object.defineProperty
在 window/global
上定义 a
属性,获取 a
属性时,会调用 get
let val = 1;
Object.defineProperty(window, 'a', {get: function() {return val++;}
});
- 办法三
var a = [1, 2, 3];
a.join = a.shift;
数组的 toString
办法返回一个字符串,该字符串由数组中的每个元素的 toString()
返回值经调用 join()
办法连贯(由逗号隔开)组成。
因而,咱们能够从新 join
办法。返回第一个元素,并将其删除。
十八 More
返回目录
还有很多基础知识,或者题目,jsliang 没精力一一增加进来了,尽量将原文放文章中了。
- [x] [[译] 送你 43 道 JavaScript 面试题](https://juejin.im/post/684490…【浏览倡议:1h】
这篇文章还是挺不错的,jsliang 做的第一遍还是错了一些题,哈哈。
那么本篇文章就到这里,祝小伙伴早日找到适合 Offer
。
<img alt=” 常识共享许可协定 ” style=”border-width:0″ src=”https://i.creativecommons.org/l/by-nc-sa/4.0/88×31.png” />
<span xmlns:dct=”http://purl.org/dc/terms/” property=”dct:title”>jsliang 的文档库 </span> 由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定进行许可。
基于 https://github.com/LiangJunro… 上的作品创作。
本许可协定受权之外的应用权限能够从 https://creativecommons.org/l… 处取得。