js事件循环机制之宏任务与微任务

  • JavaScript是单线程的语言
  • Event Loop是JavaScript的执行机制

js事件循环

js是单线程,所以js任务也要一个一个按顺序执行。如果一个任务耗时过长,那么其后的任务必须等着。这就会导致图片加载,请求数据会让降低线程效率。所以js中将任务分为了两类:

当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。关于这部分有严格的文字定义,这里就不再赘述。

如下一段代码:

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

为什么会出现这样打印顺序呢?

undefined

图片来源于网络

解读:

js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。

以下代码的执行流程如下:

1
2
3
4
5
6
7
8
9
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');

微任务(Micro tasks)与宏任务(task)

微任务和宏任务皆为异步任务,它们都属于一个队列;主要区别在于他们的执行顺序,Event Loop的走向和取值。那么他们之间到底有什么区别呢?

undefined

图片来源于网络

宏任务一般是:包括整体代码script,setTimeout,setInterval、setImmediate。

微任务:原生Promise(有些实现的promise将then方法放到了宏任务中)、process.nextTick、Object.observe(已废弃)、 MutationObserver 记住就行了。

setTimeout

大名鼎鼎的setTimeout无需再多言,大家对他的第一印象就是异步可以延时执行,我们经常这么实现延时3秒执行:

1
2
3
setTimeout(() => {
console.log('延时3秒');
},3000)

但是有时候也会出现写的延时3秒,实际却5,6秒才执行函数的情况。代码如下:

1
2
3
4
5
6
7
8
9
setTimeout(() => {
test()
},3000)

function test(){
console.log("123")
}
// 概念代码,模拟同步代码执行速度慢
sleep(10000000)

我们还经常遇到setTimeout(fn,0)这样的代码,这里的0秒并不代表能立即执行,它的的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。

1
2
3
4
5
6
7
8
9
10
11
//代码1
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},0);

//代码2
console.log('先执行这里');
setTimeout(() => {
console.log('执行啦')
},3000);

要补充的是,即便主线程为空,setTimeout的0毫秒实际上也是达不到的。根据HTML标准,最低是4毫秒。

setInterval

setInterval与setTimeout差不多,只不过setInterval是循环执行的。对于执行循序而言,setInterval会每隔特定时间将注册的函数放入Event Queue,如果前面的任务执行时间过长,同样需要等待。

Promise与process.nextTick(callback)

不同类型的任务会进入对应的Event Queue,比如setTimeoutsetInterval会进入相同的Event Queue。

例子

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
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')
})
})

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

宏任务Event Queue 微任务Event Queue
setTimeout1 process1
setTimeout2 then1

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

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

宏任务Event Queue 微任务Event Queue
setTimeout2 process3
then3

希望大家看了本篇文章都有收获 …

上次更新 2021-01-28