logo头像
Snippet 博客主题

js事件循环(Event Loop)

本文于 388 天之前发表,文中内容可能已经过时。

微任务和宏任务皆为异步任务,它们都属于一个队列,主要区别在于他们的执行顺序,Event Loop的走向和取值

Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

  • 同步任务(Synchronous Task):立即执行,先入先出。
  • 异步任务(Asynchronous Task):不会立即执行,而是放入任务队列,等待事件循环调度。

1、宏任务和微任务的区别

1
2
3
4
5
6
7
8
9
10
11
宏任务                          浏览器          Node
I/O ✅ ✅
setTimeout ✅ ✅
setInterval ✅ ✅
setImmediate ❌ ✅
requestAnimationFrame ✅ ✅

微任务
process.nextTick ❌ ✅
MutationObserver ✅ ❌
Promise.then catch finally ✅ ✅

宏任务与微任务之间的执行顺序(宏任务(整个js)->同步任务->微任务->宏任务(下一个))

1
2
3
4
5
6
7
8
9
10
11
12
setTimeout (() => { console.log(4)}) // 宏任务

new Promise (resolve => {
resolve()
console.log(1) // 同步任务1
}).then (() => {
console.log(3) // 微任务
})

console.log(2) // 同步任务2

// 1 2 3 4

下面说说执行到宏任务后是怎么继续运行的
(这里声明下,整段js代码就是第一个大的宏任务,事件循环是由这第一个宏任务开始的,然后分出微任务,这里是为了理解微任务宏任务的执行区别就先跳过这第一层)


2、事件循环中的执行顺序示意

  1. 执行同步任务,进入调用栈(Call Stack)。
  2. 同步任务执行完后:
    • 将异步任务(宏任务)加入 宏任务队列
    • 将产生的微任务加入 微任务队列
  3. 当前宏任务执行结束后:
    • 立即执行所有 微任务队列(直到队列清空)
  4. 清空微任务后,如果有新的宏任务:
    • 取下一个宏任务执行
  5. 重复上述循环。

3、举例说明执行顺序

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

setTimeout(() => {
console.log('2');
}, 0);

Promise.resolve().then(() => {
console.log('3');
});

console.log('4'); // 1 4 3 2

执行步骤:

  1. 进入主脚本(这是一个宏任务),执行同步代码:

    • 输出 'script start'
    • 注册 setTimeout → 放入宏任务队列
    • 注册 Promise.then → 放入微任务队列
    • 输出 'script end'
  2. 当前宏任务(整个脚本)执行完毕 → 清空

    微任务队列:

    • 输出 'Promise'
  3. 进入下一个宏任务:

    • 执行 setTimeout 回调 → 输出 'setTimeout'

2、场景

去银行办存钱业务,先取号再排队。 “您的号码为XX,前边还有XX人。”

银行柜台前排着一条队伍,都是存钱的人,存钱属于宏任务,这条队伍就是宏任务队列。

当一个“大爷”被叫到了自己的号码,就上前去–被处理,处理存钱业务时,‘宏大爷’突然想给自己的存款办个微理财(微任务),那么银行职员就将他的需求添加到自己的微任务队列,大爷就不用再排队了,直接在存钱宏任务进行完后就处理衍生出来的微任务理财,办理财时大爷又说办个信用卡,那就又排到微任务队列里。当大爷的微任务(办理理财和办信用卡)没有办理完,是不会让让下一个人办理业务的**(即微任务没执行完是不会执行下一个宏任务的)**。

但要是在此次存钱时‘宏大爷’说他还要存钱,且是他老伴要存钱,也是宏任务,但不好意思,需要取号到宏任务队列的后面排队(这里就是在宏任务进行时产生微任务和宏任务的处理方式)。

image-20230417002550329


3、案例

结合下面的题目理解理解(这里先不介绍node环境的事件循环的特殊地方,主要以浏览器环境):

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
<script>
setTimeout(function () { //宏任务1
console.log('1');
});

new Promise(function (resolve) {
console.log('2'); //同步任务1
resolve();
}).then(function () { //微任务1
console.log('3');
});

console.log('4'); //同步任务2

setTimeout(function () { //宏任务2
console.log('5'); //宏任务2中的同步任务
new Promise(function (resolve) {
console.log('6'); //宏任务2中的同步任务
new Promise(function (resolve) { //宏任务2中的微任务
console.log('x1');
resolve();
}).then(function () {
console.log('X2');
});
setTimeout(function () { //宏任务2中的宏任务
console.log('X3');
new Promise(function (resolve) { //宏任务2中的宏任务中的同步任务
console.log('X4');
resolve();
}).then(function () { //宏任务2中的宏任务中的微任务
console.log('X5');
});
})
resolve();
}).then(function () { //宏任务2中的微任务
console.log('7');
});
})

setTimeout(function () { //宏任务3
console.log('8');
});
//(对于这段代码node环境和浏览器环境输出一致)
//输出答案:2,4,3,1,5,6,x1,x2,7,8,x3,x4,x5
</script>