事件循环,画个圈圈……

浏览器的事件循环机制

浏览器事件循环图(摘自《javascript忍者秘籍第二版》13章):

宏任务微任务

宏任务:从浏览器角度,代表一个离散的,独立的工作单元
比如:创建主文档对象,解析HTML,执行主线(全局)js代码
更改当前URL和各种事件(页面加载,网络事件,定时器等)

微任务:是更小的任务,微任务要尽可能快的,通过异步方式执行
比如:DOM变化(MutationObserver),Promise回调函数
由于因为async await 本身就是promise+generator的语法糖,所以await后面的也是

从面试题理解运行机制

1
2
3
4
5
6
7
8
9
10
11
12

// 写出输出
setTimeout(function () {
console.log(1);
});
new Promise(function(resolve,reject){
console.log(2)
resolve(3)
}).then(function(val){
console.log(val);
})
console.log(4);

上面的代码执行结果为:2,4,3,1
我们结合上面的事件循环图来理解下输出结果

  1. 首先执行主线(全局js代码),碰到定时器代码,定时器时间为0表示尽快执行
    定时器是宏任务,于是把定时器任务放入宏任务队列
    现在宏任务队列中是(主线,定时器)
  2. 接着往下解析,开始执行promise的代码,遇到promise里的2直接输出
  3. promise中有一个异步方法,是微任务,于是把他放到微任务队列
  4. 继续往下执行全局代码,输出4
  5. 跑完上面的流程,js主线(全局)代码执行完成,同时也是第一个宏任务执行完成了
  6. 按照事件循环图的流程,开始检查微任务队列是否有任务,有的话执行所有微任务,输出3
  7. 第一轮循环结束,更新UI主线(全局)任务从宏任务中出队
  8. 开始新的一轮事件循环,执行队首的宏任务即之前的定时器任务输出1

通过上面的例子我们也可以知道,定时器的执行时间机制:
因为js单线程的本质,我们只能控制定时器何时被加入宏任务队列,而无法控制其何时执行

再看一个例子:

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

//请写出输出内容
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}

console.log('script start');

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

async1();

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


/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

上面这个例题可能会有一些地方难理解,主要是async和await

1
2
3
4
5
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}

等价于:

1
2
3
4
5
6
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(() => {
console.log('async1 end');
})
}

nodejs的事件循环机制