随便看技术文章时发现关于事件循环 (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
您还可以尝试调整 while 阻塞语句的位置及阻塞时间, 以便加深理解
来源: http://www.jianshu.com/p/b620a9a26f8d