Javascript 事件循环 是指浏览器或Node的一种解决JS单线程运行时不会阻塞的一种机制

简单来说,Event Loop(事件循环)是js的执行机制,当从script标签开始执行就会分 异步任务和同步任务,同步任务会直接进入主线程依次执行,异步任务会分宏任务和微任务,在一次事件循环中,微任务队列会先执行完,再执行宏任务,这样就完成了一个事件循环,然后才执行下一个事件循环。

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(1)

setTimeout(function(){
console.log(2)
},0)

console.log(3)

执行结果:
// 1 3 2

分析:
1.console.log(1)是同步任务,直接打印1
2.setTimeout是异步任务,且是宏函数,放到宏函数队列中,等待下次Event Loop才会执行;
3.console.log(3)是同步任务,直接打印3
4.主线程执行完毕,没有微任务,那么执行第二个宏任务setTimeout,打印2
5.结果:132

示例2:

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
setTimeout(function(){
console.log(1)
});

new Promise(function(resolve){
console.log(2);
for(var i = 0; i < 10000; i++){
i == 9999 && resolve();
}
}).then(function(){
console.log(3)
});

console.log(4);

执行结果:
// 2, 4, 3, 1

分析:
1.setTimeout是异步,且是宏函数,放到宏函数队列中;
2.new Promise是同步任务,直接执行,打印2,并执行for循环;
3.promise.then是微任务,放到微任务队列中;
4.console.log(4)同步任务,直接执行,打印4
5.此时主线程任务执行完毕,检查微任务队列中,有promise.then,执行微任务,打印3
6.微任务执行完毕,第一次循环结束;从宏任务队列中取出第一个宏任务到主线程执行,打印1
7.结果:2431

示例3:

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
console.log(1);

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

Promise.resolve().then(function() {
console.log(3);
}).then(function() {
console.log('4.我是新增的微任务');
});

console.log(5);

执行结果:
// 1,5,3,4.我是新增的微任务,2

分析:
1.console.log(1)是同步任务,直接执行,打印1
2.setTimeout是异步,且是宏函数,放到宏函数队列中;
3.Promise.resolve().then是微任务,放到微任务队列中;
4.console.log(5)是同步任务,直接执行,打印5
5.此时主线程任务执行完毕,检查微任务队列中,有Promise.resolve().then,执行微任务,打印3
6.此时发现第二个.then任务,属于微任务,添加到微任务队列,并执行,打印4.我是新增的微任务;
7.这里强调一下,微任务执行过程中,发现新的微任务,会把这个新的微任务添加到队列中,微任务队列依次执行完毕后,才会执行下一个循环;
8.微任务执行完毕,第一次循环结束;取出宏任务队列中的第一个宏任务setTimeout到主线程执行,打印2
9.结果:1534.我是新增的微任务,2

示例4:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function add(x, y) {
console.log(1)
setTimeout(function() { // timer1
console.log(2)
}, 1000)
}
add();

setTimeout(function() { // timer2
console.log(3)
})

new Promise(function(resolve) {
console.log(4)
setTimeout(function() { // timer3
console.log(5)
}, 100)
for(var i = 0; i < 100; i++) {
i == 99 && resolve()
}
}).then(function() {
setTimeout(function() { // timer4
console.log(6)
}, 0)
console.log(7)
})

console.log(8)

执行结果
//1,4,8,7,3,6,5,2

分析:
1.add()是同步任务,直接执行,打印1
2.add()里面的setTimeout是异步任务且宏函数,记做timer1放到宏函数队列;
3.add()下面的setTimeout是异步任务且宏函数,记做timer2放到宏函数队列;
4.new Promise是同步任务,直接执行,打印4
5.Promise里面的setTimeout是异步任务且宏函数,记做timer3放到宏函数队列;
6.Promise里面的for循环,同步任务,执行代码;
7.Promise.then是微任务,放到微任务队列;
8.console.log(8)是同步任务,直接执行,打印8
9.此时主线程任务执行完毕,检查微任务队列中,有Promise.then,执行微任务,发现有setTimeout是异步任务且宏函数,记做timer4放到宏函数队列;
10.微任务队列中的console.log(7)是同步任务,直接执行,打印7
11.微任务执行完毕,第一次循环结束;
12.检查宏任务Event Table,里面有timer1、timer2、timer3、timer4,四个定时器宏任务,按照定时器延迟时间得到可以执行的顺序,即Event Queue:timer2、timer4、timer3、timer1,取出排在第一个的timer2;
13.取出timer2执行,console.log(3)同步任务,直接执行,打印3
14.没有微任务,第二次Event Loop结束;
15.取出timer4执行,console.log(6)同步任务,直接执行,打印6
16.没有微任务,第三次Event Loop结束;
17.取出timer3执行,console.log(5)同步任务,直接执行,打印5
18.没有微任务,第四次Event Loop结束;
19.取出timer1执行,console.log(2)同步任务,直接执行,打印2
20.没有微任务,也没有宏任务,第五次Event Loop结束;
21.结果:14873652

示例5:

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

setTimeout(function() { // timer1
console.log(1);
setTimeout(function() { // timer3
console.log(2);
})
}, 0);
setTimeout(function() { // timer2
console.log(3);
}, 0);

执行结果
//1,3,2

分析:
1.第一个setTimeout是异步任务且宏函数,记做timer1放到宏函数队列;
2.第三个setTimeout是异步任务且宏函数,记做timer2放到宏函数队列;
3.没有微任务,第一次Event Loop结束;
4.取出timer1,console.log(1)同步任务,直接执行,打印1
5.timer1里面的setTimeout是异步任务且宏函数,记做timer3放到宏函数队列;
6.没有微任务,第二次Event Loop结束;
7.取出timer2,console.log(3)同步任务,直接执行,打印3
8.没有微任务,第三次Event Loop结束;
9.取出timer3,console.log(2)同步任务,直接执行,打印2
10.没有微任务,也没有宏任务,第四次Event Loop结束;
11.结果:132

示例6:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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,7,6,8,2,4,3,5,9,11,10,12

分析:
1. console.log(1)是同步任务,直接执行,打印 1
2. setTimeout是异步,且是宏函数,放到宏任务队列中,暂且记为setTimeout1;
3. process.nextTick() 是微任务,放到微任务队列中,暂且记为process1;
4. new Promise是同步任务,直接执行,打印 7
5. Promise.then是微任务,放到微任务队列, 暂且记为 then1;
6. setTimeout是异步,且是宏函数,放到宏任务队列中,暂且记为setTimeout2;(此时第一轮事件循环宏任务执行完成,发现了两个微任务)
7. 执行 process1,打印 6;
8. 执行 then1,打印 8; (此时第一轮事件循环正式结束)
9. console.log('2') 是同步任务,直接执行,打印 2
10. 遇到 process.nextTick() 是微任务,放到微任务队列中,暂且记为process2;
11. new Promise是同步任务,直接执行,打印 4
12. Promise.then是微任务,放到微任务队列, 暂且记为 then2;(此时第二轮事件循环宏任务执行完成,发现了两个微任务)
13. 执行 process2,打印 3;
14. 执行 then2,打印 5; (此时第二轮事件循环正式结束)
15. console.log('9') 是同步任务,直接执行,打印 9
16. 遇到 process.nextTick() 是微任务,放到微任务队列中,暂且记为process3;
17. new Promise是同步任务,直接执行,打印 11
18. Promise.then是微任务,放到微任务队列, 暂且记为 then3;(此时第三轮事件循环宏任务执行完成,发现了两个微任务)
19. 执行 process3,打印 10
20. 执行 then3,打印 12

注意: process对象是 Node 的一个全局对象,提供当前 Node 进程的信息。它可以在脚本的任意位置使用,不必通过require命令加载。该对象部署了EventEmitter接口

宏任务(macro-task):整体代码script、setTimeOut、setInterval
微任务(mincro-task):promise.then、process.nextTick(node)

参考文章

js运行机制详解(Event Loop)

这一次,彻底弄懂 JavaScript 执行机制