随便看技术文章时发现关于事件循环(Event Loop)方面的知识还是很混乱,于是多方查阅了相关资料,在此做下笔记,如若理解有误还望各位看官指出
首先我们需要了解几个名词:Event Loop,执行栈,任务队列,宏任务(macro),微任务(micro)。
- Event Loop:你可以近似的理解为:<script> 标签内的代码自上而下执行一遍为一个循环 Event Loop。异步任务会被推到下一个 Event Loop 去执行。
- 执行栈:当前执行 Event Loop 的任务顺序
- 任务队列:全部 Event Loop 的任务执行顺序,包括宏任务队列和微任务队列
- 宏任务:当前 Event Loop 中按顺序执行的任务,可以有多个宏任务队列
- 微任务:当前 Event Loop 中所有宏任务执行之后再执行,只能有一个微任务队列
下面是一个 Event Loop 执行过程的概述,这里暂不考虑定时器的延时操作:
- 开始执行 script 中代码,即进入第一轮 Event Loop
- 遇到同步的 macro_1,直接执行,比如单独的打印语句 console.log()
- 遇到 micro_1,推到本轮 Event Loop 末尾,比如 Promise 的 then 中的任务
- 遇到异步 macro_2,推到第二轮 Event Loop
- 遇到异步 micro_2,推到第二轮 Event Loop ,macro_2 之后
- 遇到同步的 macro_3,直接执行
- 遇到 micro_3,推到本轮 Event Loop 末尾,在 micro_1 之后
- 遇到异步 macro_4,推到第三轮 Event Loop
这么说看着有些晕,我们不妨举个栗子:
new Promise(function(resolve) {
console.error("macro_1"); // 第一轮宏任务 1
resolve()
}).then(function() {
console.log("micro_1"); // 第一轮微任务 1
})
setTimeout(function() {
new Promise(function(resolve) {
console.error("macro_2");// 第二轮宏任务
resolve()
}).then(function() {
console.log("micro_2"); // 第二轮微任务
})
})
new Promise(function(resolve) {
console.error("macro_3"); // 第一轮宏任务 2
resolve()
}).then(function() {
console.log("micro_3"); // 第一轮微任务 2
})
setTimeout(function() {
new Promise(function(resolve) {
console.error("macro_4"); // 第三轮宏任务
resolve()
}).then(function() {
console.log("micro_4"); // 第三轮微任务
})
})
结果:
结果
上面的例子执行过程就与之前阐述的基本一致:
- 第一轮:宏1 - 宏2 - 微1 - 微2
- 第二轮:宏 - 微
- 第三轮:宏 - 微
有了上面的基础,我们来看一个大一点的栗子:
console.error('macro-start')
setTimeout(function() {
console.log('timeout1')
new Promise(function(resolve) {
console.log('timeout1_promise')
resolve()
}).then(function() {
console.log('timeout1_then')
})
}, 2000)
for(let i = 0; i <= 5; i++) {
setTimeout(function() {
console.log("inner_" + i)
}, i * 1000)
console.log(i)
}
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.warn("micro-start")
const delay = +new Date();
while(+new Date() - delay < 1000);
console.log('then1')
})
setTimeout(function() {
console.log('timeout2')
new Promise(function(resolve) {
console.log('timeout2_promise1')
resolve()
}).then(function() {
console.log('timeout2_then1')
})
new Promise(function(resolve) {
console.log('timeout2_promise2')
resolve()
}).then(function() {
console.log('timeout2_then2')
})
}, 1000)
new Promise(function(resolve) {
console.log('promise2')
resolve()
}).then(function() {
console.log('then2')
console.warn("micro-end")
})
console.error("macro-end")
让我们一步步的分析:
开始 Event Loop:
第一个打印:没啥说的,直接打印
console.error('macro-start')
- 接着是个
2s
的setTimeout
,异步操作,2s
后推入任务队列,当前事件循环没完事,先不管 -
for
循环,包括了setTimeout
和console.log()
两个语句,setTimeout
没有设置时间参数,直接推入任务队列,别的先不管,因此
第二个打印:for 循环中定时器外的 console.log()
0,1,2,3,4,5
- 再往下读,遇到一个
Promise
,宏任务,直接打印
第三个打印:Promise 中的 console.log()
console.log('promise1')
-
Promise
后有个then
,微任务,先不管 - 接着又是一个
1s
的setTimeout
,1s
后推入任务队列,同样不管 - 然后是
Promise
第四个打印:Promise 中的 console.log()
console.log('promise2')
- 之后的
then
同样道理,微任务,Pass,往后看
第五个打印:全局的 console.error()
console.error("macro-end")
- 至此,本次 Event Loop 的所有 宏任务 已经执行完毕,正如之前所说,微任务是同一轮宏任务执行完之后才开始执行,而本轮共有两个微任务,先看第一个
then
:
第六个打印:then 中的 console.warn()
console.warn("micro-start")
- 之后是一个阻塞
1s
语句,即1s
后进行后面的操作:
第七个打印:while 阻塞语句之后的 console.log()
# 1s later
console.log('then1')
- 然后是 Event Loop 的第二个微任务
then
第八个打印:then 中的 console.log()
console.log('then2')
第九个打印:then 中的 console.warn()
console.warn("micro-end")
- 此时,Event Loop 已全部执行完毕,将开启新一轮 Event Loop,回到顶部再来一次。
- 从头再读,先是那个
2s
的setTimeout
,在第一轮读到这个语句时已经开始计时,2s
后将任务推入任务队列,而第一轮中有个while
的1s
阻塞,实际上再过1s
就应该执行其中的语句了,先记着这个,我们继续往下看 - 又回到了
for
循环,这其中有个时间参数为0
的setTimeout
,就是第一轮时直接推入任务队列,但由于当时第一轮的宏任务和微任务还没有执行完,因此一直被卡着。现在第一轮任务已经执行完了,因此
第十个打印:for 中时间参数为 0 的 setTimeout
console.log("inner_" + i) // console.log("inner_0")
-
for
循环中还有个时间参数为1s
的setTimeout
,注意,正如上面所说,setTimeout
是执行时就开始计时,1s
后推入任务队列。而第一轮的while
阻塞了1s
,因此这里将不再等待1s
才打印,而是直接打印。
第十一个打印:for 中时间参数为 1s 的 setTimeout
console.log("inner_" + i) // console.log("inner_1")
- 接着是
1s
的setTimeout
第十二个打印,1s 的问题跟上面的情况一样,因此不再赘述
console.log('timeout2')
- 其实你可以将
1s
的这个setTimeout
内的部分但拿出来看,这样层次清晰一些:
console.log('timeout2')
new Promise(function(resolve) {
console.log('timeout2_promise1')
resolve()
}).then(function() {
console.log('timeout2_then1')
})
new Promise(function(resolve) {
console.log('timeout2_promise2')
resolve()
}).then(function() {
console.log('timeout2_then2')
})
- 参考第三、四、七、八四个打印可知,这里应该是:
第十三个打印:
console.log('timeout2_promise1')
第十四个打印:
console.log('timeout2_promise2')
第十五个打印:
console.log('timeout2_then1')
第十六个打印:
console.log('timeout2_then2')
- 之后没有立即打印的结果出来,记得刚才那个
2s
的setTimeout
吗?到它了。因为第一轮时setTimeout
已经开始计时,而while
已经阻塞了一秒,因此这里只需要再等1s
就会执行。
第十七个打印:
# 1s later
console.log('timeout1')
- 然后继续
第十八个打印:
console.log('timeout1_promise')
第十九个打印:
console.log('timeout1_then')
- 之后又到了
for
循环,打印时间参数为2s
的setTimeout
中的内容
第二十个打印:
console.log("inner_" + i) //console.log("inner_2")
- 接下来就只剩下
for
循环中的时间参数为3,4,5
的三个setTimeout
了,因此最后的打印为
第二十一个打印:
# 1s later
console.log("inner_" + i) //console.log("inner_3")
# 1s later
console.log("inner_" + i) //console.log("inner_4")
# 1s later
console.log("inner_" + i) //console.log("inner_5")
整理一下,完整的打印顺序如下
macro-start
0
1
2
3
4
5
promise1
promise2
macro-end
micro-start
-------------- 1s later --------------
then1
then2
micro-end
inner_0
inner_1
timeout2
timeout2_promise1
timeout2_promise2
timeout2_then1
timeout2_then2
-------------- 1s later --------------
timeout1
timeout1_promise
timeout1_then
inner_2
-------------- 1s later --------------
inner_3
-------------- 1s later --------------
inner_4
-------------- 1s later --------------
inner_5
网友评论