浏览器事件环
代码执行顺序:默认先执行主栈中的代码,主栈执行完成后清空微任务对列,微任务对列清空后,取出第一个宏任务到主栈中执行,执行完成后再去检查微任务对列是否有未执行任务,如果有清空,若没有取下一个宏任务到主栈中执行,执行完成后再去检查微任务对列是否有未执行任务,如果有清空...形成一个事件环
栈(Stack)
是一种遵循后进先出(last in first out LIFO)原则的有序集合;在栈里,新元素都靠近栈顶,旧元素都靠近栈底(现实中栈的例子:一摞书或者堆放在一起的盘子)
- 栈顶:新添加的或待删除的元素保存的一端
队列(Queue)
是一种遵循先进先出(first in first out FIFO)原则的一组有序的项;在队列中,最新添加的元素必须排在队列的末尾(现实中队列的例子:排队过安检)
宏任务
setTimeoutsetImmediate(只有IE支持)- 比
setTimeout快
- 比
messageChannel- 在
web worker中可用 - 允许创建一个新的消息通道,并通过它的两个
MessagePort属性发送数据
- 在
白话说明:我们小的时候都玩过土电话,我们可以新的消息通道想象成我们的土电话,把两个
MessagePort属性想像成图中的小熊(port2)和小兔(port1),当小兔通过土电话向小熊喊话(port1向另个port2发送数据),声音的传递过程(数据传递的过程就是)就是异步的。
<html>
<body>
<div id="app"></div>
<script>
console.log('start')
// 这里我们为了测试他是异步的,我们采用port1先发送数据,port2后监听
let channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
port2.postMessage('你好')
setTimeout(()=>{
console.log('setTimeout');
},0)
port1.onmessage = function (e) {
console.log(e);
}
console.log('end')
</script>
</body>
</html>
微任务
Promise.resolve.thenMutationObserverDOM3 Events规范的一部分- 提供了监视对
DOM树所做更改的能力
<html>
<body>
<div id = "app"></div>
<script>
/* 实现 等待dom更新后 再取dom中的数据 */
setTimeout(()=>{
console.log('timeout');
},0)
// 1.创建一个MutationObserver的实例并传入一个异步的callback
let mutationObserver = new MutationObserver(()=>{
console.log(app.children.length); // 异步执行
})
// 2.观察app的childList的变化
mutationObserver.observe(app,{childList:true})
// 3.通过脚本动态向app中插入30个p
for (let i = 0; i < 10; i++) {
app.appendChild(document.createElement('p'));
}
for (let i = 0; i < 10; i++) {
app.appendChild(document.createElement('p'));
}
for (let i = 0; i < 10; i++) {
app.appendChild(document.createElement('p'));
}
</script>
</body>
</html>
小题开胃
console.log('start')
setTimeout(()=>{
console.log(1);
Promise.resolve().then(()=>{
console.log(2)
})
},0);
Promise.resolve().then(data=>{
console.log(3);
setTimeout(()=>{
console.log(4);
})
})
console.log('end');
// start end 3 1 2 4
- 图文解析
难题巩固
async function async1() {
console.log('async1Start');
await async2();
console.log('async1End');
}
async function async2() {
console.log('async2');
}
console.log('scriptStart');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('scriptEnd');
//scriptStart async1Start async2 promise1 scriptEnd async1End promise2 setTimeout
- 图文解析
思考题
- 分析以下代码如何输出?为什么?
let guYan = async () => {
console.log(await new Promise((resolve, reject) => {
console.log('guYan');
}))
}
guYan()
注:前面的两道题的分析中没加入宏、微任务事件池的概念(就是在进入队列之前需要先经过事件池的排列)
node事件环
- 每个阶段都有一个自己的队列
- 在低版本
node中在主执行栈执行完毕和切换队列的时候清空微任务 - 在高版本(11+)
node中和浏览器一样在主执行栈执行完毕和一个宏任务执行完毕后清空微任务
// 我们排除掉node内部自己调用的循环部分,其实需要我们掌握的部分并不多,我总结为三部分
// 1.timer阶段主要代表setTimeout
// 2.poll 轮循阶段 主要是i/o的读写(fs模块的一些操作等)
// 3.check阶段主要代表setImmediate
┌───────────────────────────┐
┌─>│ 1.timers │──┐
│ └─────────────┬─────────────┘ │ ┌───────────────┐ |
│ ┌─────────────┴─────────────┐ │ │ incoming: │ | 执
│ │ 2.poll │<─────┤ connections, │ | 行
│ └─────────────┬─────────────┘ │ │ data, etc. │ | 顺
│ ┌─────────────┴─────────────┐ │ └───────────────┘ | 序
└──│ 3.check │<─┘ ↓
└───────────────────────────┘
/*
*执行顺序分析:
* 1.从timer阶段开始检查是否有可执行的若没有向下执行
* 2.在poll阶段执行完毕后检查timer阶段是否待执行的,若有准备跳回timer阶段执行;
* 但是在这之前需要检查check阶段是否有待执行的,若有先跳到check阶段执行,再跳到timer阶段执行,若没有可直接跳回timer阶段执行;
* 3.无条件按照顺序执行,没有可跳过
*/
宏任务
setTimeoutsetImmediatesetIntervalreadFile文件读写系列
微任务
promise.thenprocess.nextTick
案例解析
案例一
setTimeout(()=>{
console.log('setTimeout');
},0)
setImmediate(()=>{
console.log('setImmediate');
})
- 上面的代码你频繁的在
node环境中执行你会发现两种结果- 1.先输出
setTimeout再输出setImmediate,这种情况我不做说明哈就是正常情况 - 2.先输出
setImmediate再输出setTimeout,这种情况的原因是:当我们程序运行的时候我们知道setTimeout的第二个参数代表时间虽然我们写成0但是最小时间并不是0,大约是4。所以当执行到timer阶段的时候setTimeout的执行时间还没到,直接进行到poll阶段等待,等待一段时候后发现timer阶段的setTimeout可以执行,准备回到timer阶段执行,但是回去之前需要检查check阶段,发现有setImmediate需要执行
- 1.先输出
案例二
let fs = require('fs');
fs.readFile(__filename,'utf-8',(err,data)=>{
setTimeout(()=>{
console.log('setTimeout')
},0)
setImmediate(()=>{
console.log('setImmediate')
})
)
- 程序运行发现
timer阶段没有可执行的,直接进入poll阶段,在poll阶段分别给timer阶段和check阶段增加一个待执行函数后开始等待setTimeout的执行,当setTimeout可执行后,准备从poll阶段跳到timer之前检查check阶段有可执行的setImmediate所以永远是先输出setImmediate后输出setTimeout
小概念总结
进程和线程的关系
- 进程:计算机分配任务和调度任务的最小单位(进程是线程的集合)
js中一个进程只有一个主线程- 中
js线程和UI线程是互斥的
- 中
如何提高js代码的加载速度
defer- 表示脚本会被延迟到文档完全被解析和显示之后再执行,加载后续文档元素的过程将和
js脚本的加载并行进行(即实现了异步加载js脚本),这样页面加载会变快
<script src="index.js" defer></script>- 表示脚本会被延迟到文档完全被解析和显示之后再执行,加载后续文档元素的过程将和
async(h5属性)- 脚本一旦可用,执行就会异步
- 不保证脚本的加载顺序按照引入的顺序,因此一旦使用就要保证所需加载的
js脚本无依赖关系
<script src="index.js" async></script>preload(会先加载资源)prefetch(默认不会马上加载,等待浏览器空闲时偷偷加载)