简单的认识事件循环

先看一段代码:(先热个身)

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});

console.log('script end');

打印顺序是什么? 正确答案是:script start, script end, promise1, promise2, setTimeout

没关系 继续看

需要知道的专业名词术语:

synchronous:同步任务
asynchronous:异步任务
task queue/callback queue:任务队列
execution context stack:执行栈
heap:堆
stack:栈
macro-task:宏任务
micro-task:微任务

首先我们还要知道两点:
JavaScript是单线程的语言
Event Loop是javascript的执行机制

异步任务包括:宏任务 和 微任务

同步任务:promise内的函数属于同步 .then()内部才属于异步微服务

new Promise(function(resolve) {
console.log(‘7’); //属于同步任务
resolve();
}).then(function() {
console.log(‘8’) //属于异步微服务
})

宏任务:

包括整体代码script
setTimeout
setInterval
setImmediate

微任务:

原生Promise(有些实现的promise将then方法放到了宏任务中)
process.nextTick (process.nextTick(callback)类似node.js版的"setTimeout")
Object.observe(已废弃)
MutationObserver

记住就行了。

事件循环规则:

骚年 先接我八字真言:先同后异,先微后宏

实例:

Promise.resolve().then(()=>{ 
  console.log('Promise1')   
  setTimeout(()=>{
    console.log('setTimeout2')
    Promise.resolve().then(()=>{
      console.log('Promise3')  
      setTimeout(()=>{
        Promise.resolve().then(()=>{
          console.log('Promise5')  
          setTimeout(()=>{
            console.log('setTimeout6')
          },0)
        })
        console.log('setTimeout4')
      },0)
    })
  },0)
})
  1. 先从上到下执行同步任务并全部执行完成

  2. 查找异步任务并分配到微任务列表 和 宏任务列表 (每个列表里先进栈先执行

  3. 执行微服务,并且查找微服务内部的 异步任务然后分配到微任务列表 和 宏任务列表

  4. 执行宏任务,并且查找微服务内部的 异步任务然后分配到微任务列表 和 宏任务列表

  5. 既然是事件循环就不是一次就执行完毕了的,因为异步任务会有嵌套,最外层事件最先暴露出来,首先进入第一次循环

  6. 再先执行微服务,并查找,分配

  7. 执行宏任务,并查找,分配

  8. 依次循环直至没有异步任务

  9. 执行结束

1
2
3
4
5
6
7
8
9
10
11
12
13
Promise.resolve().then(()=>{
console.log('Promise1')
setTimeout(()=>{
console.log('setTimeout2')
},0)
})

setTimeout(()=>{
console.log('setTimeout1')
Promise.resolve().then(()=>{
console.log('Promise2')
})
},0)

第一回合 列出所有异步事件并逐个分配到宏服务和微任务里去,然后执行 先微后宏

微:promise1   
宏:setTimeout1
先打印Promise1,setTimeout2进宏任务列表
setTimeout1,Promise2进微任务列表

第二回合 执行异步列表 先微后宏

先打印Promise2,
再打印setTimeout2,

到这里是不是有个大概的了解了,测验来了:(先用纸笔根据自己理解先答一遍,然后再看答案)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
console.log('1');

setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})

setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})

第一轮事件循环流程分析如下:

  1. 整体script作为第一个宏任务进入主线程,遇到console.log,输出1。
  2. 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。我们暂且记为setTimeout1。
  3. 遇到process.nextTick(),其回调函数被分发到微任务Event Queue中。我们记为process1。
  4. 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。我们记为then1。
  5. 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,我们记为setTimeout2。
宏任务Event Queue 微任务Event Queue
setTimeout1 process1
setTimeout2 then1
  1. 上表是第一轮事件循环宏任务结束时各Event Queue的情况,此时已经输出了1和7。

我们发现了process1和then1两个微任务。

  1. 执行process1,输出6。
  2. 执行then1,输出8。

好了,第一轮事件循环正式结束,这一轮的结果是输出1,7,6,8。那么第二轮时间循环从setTimeout1宏任务开始:

  1. 首先输出2。接下来遇到了process.nextTick(),同样将其分发到微任务Event Queue中,记为process2。
  2. new Promise立即执行输出4,then也分发到微任务Event Queue中,记为then2
宏任务Event Queue 微任务Event Queue
setTimeout2 process3
then3
  1. 第三轮事件循环宏任务执行结束,执行两个微任务process3和then3。
  1. 输出10。
  1. 输出12。
  1. 第三轮事件循环结束,第三轮输出9,11,10,12。
  1. 整段代码,共进行了三次事件循环,完整的输出为1,7,6,8,2,4,3,5,9,11,10,12。(请注意,node环境下的事件监听依赖libuv与前端环境不完全相同,输出顺序可能会有误差)

结尾

听到别人在讨论事件循环,我很想帮他们解答,可是我给出的答案都睡服不了我自己。所以决定自己整理一篇,我觉得可以让人理解的文章。下面还有别人的文章,建议大家多看看。广开言路,纳百家之言,行天下大事。

⬇⬇⬇⬇⬇下方有补充




奉上文章: JS事件循环机制(event loop)之宏任务/微任务


如有侵权行为,请点击这里联系我删除

如发现疑问或者错误点击反馈




补充

2019年4月28号:有一个地方被我忽略了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Promise(function(resolve) {
console.log('6');
setTimeout(function () {
console.log('7');
resolve();
},0)
// resolve();
}).then(function() {
console.log('8')
})


>6
>7
>8

resolve()如果写在定时器里面了,会使then处于等待状态,如果写在定时器外面这输出:6 8 7


2019年5月22号: 练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
new Promise((resolve, reject) => {
console.log("promise1")
resolve()
}).then(() => {
console.log("then11")
new Promise((resolve, reject) => {
console.log("promise2")
resolve()
}).then(() => {
console.log("then21")
}).then(() => {
console.log("then23")
})
}).then(() => {
console.log("then12")
})


promise1
then11
promise2
then21
then12
then23

下面是说明用例

new Promise((resolve, reject) => {
                console.log("1")
                resolve()
            }).then(() => {
                console.log("2")
                setTimeout(function() {
                    console.log("22")
                }, 0)
                new Promise((resolve, reject) => {
                    console.log("3")
                    resolve()
                }).then(() => {
                    console.log("4")
                }).then(() => {
                    console.log("5")
                }).then(() => {
                    console.log("12")
                })
            }).then(() => {
                console.log("6")
            }).then(() => {
                console.log("11")
            }).then(() => {
                console.log("13")
            })

总结:
1.promise后面的n多个then 在同一个事件回合里 由上到下 依次加入微列表

  1. 内外promise的同位then(两个promise的第2个then,由外到内,依次添加到微列表)