本文是近期学习js事件循环后的总结,比较适合入门,但不一定准确,欢迎指正。文章内容大量参考以下博文,非常推荐阅读:
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
https://juejin.im/post/6844903512845860872
https://github.com/aooy/blog/issues/5
html5 Event Loop定义
Event loop用来协调事件、用户交互、脚本、呈现、网络等,说白了就是控制js什么时候应该做什么事情。需要注意的是,event loop在nodejs和浏览器中处理方式不同,本文特指浏览器环境(一些老版本浏览器也先不提😢)。
猜猜看下面这段代码会输出什么:
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
在详细解释前,先说明几个概念:
-
单线程
js是单线程语言,语句会按照出现的顺序依次执行(web worker会启多个线程,但是每个线程会对应一个event loop,各自独立运行,互不影响)。 -
Task
任务
Task
主要包括ajax
的回调函数、各种事件、setTimeout
等等。所谓Task Queue
是指存放任务的队列,队列中的任务会按顺序执行,但是两个任务之间可能穿插其它工作,比如渲染。
ps:Task
在一些博客中被叫做MacroTask
宏任务,但是规范中并没有出现MacroTask
关键字,所以本文还是称为Task
。 -
MicroTask
微任务
MicroTask
通俗的说就是需要在当前Task
执行结束后立即执行的任务,主要包括MutationObserver
、Promise.then
、Object.observe
。MicroTask Queue
是一个与Task Queue
相互独立的队列,用来存放MicroTask
。每一个Task
中产生的MicroTask
都将会添加到MicroTask Queue
中,MicroTask
中产生的MicroTask
也将会添加至当前队列的尾部。
概念比较枯燥,结合一张图来理解:
图片出处
关于之前的demo,执行过程如下(超详细版):
- 第一轮:
1、代码被添加到 Task Queue
中
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script | - | - |
2、JS Stack
获取到 Task Queue
的第一个任务,开始执行(任务执行完成后才会从队列中移除):
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script | script | - |
3、打印 script start
4、执行到 setTimeout
时,将回调添加到 Task Queue
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script, setTimeout | script | - |
5、执行到 Promise.resolve().then
时,将回调添加到 MicroTask Queue
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script, setTimeout | script | then1 |
6、打印 script end
7、此时代码执行完成, JS Stack
为空
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script, setTimeout | - | then1 |
8、 MicroTask Queue
为空吗?否。所以从 MicroTask Queue
取出 Promise.resolve().then
的回调执行。
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script, setTimeout | then1 | then1 |
9、打印 promise1
10、遇到 Promise.resolve().then.then
,添加到 MicroTask Queue
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script, setTimeout | then1 | then1, then2 |
11、此时代码执行完成, JS Stack
为空
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script, setTimeout | - | then2 |
12、MicroTask Queue
为空吗?否。所以从 MicroTask Queue
取出 then2
执行,打印 promise2
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
script, setTimeout | then2 | then2 |
13、JS Stack
和 MicroTask Queue
均为空,本轮结束:
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
setTimeout1 | - | - |
- 第二轮:
1、Task Queue
取出 setTimeout
回调执行,打印 setTimeout
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
setTimeout | setTimeout | - |
2、全部执行完毕
Task Queue | JS Stack | MicroTask Queue |
---|---|---|
- | - | - |
尝试一下更复杂的例子,分析时记得在本子上写下Task Queue | JS Stack | MicroTask Queue
,记录它们的变化:
console.log('1');
setTimeout(function() {
console.log('2');
setTimeout(function(){console.log('3')})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
console.log('6')
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
new Promise(function(resolve) {
setTimeout(function(){console.log('10')})
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
结果是:1 6 7 8 2 4 5 9 11 12 3 10
,轮数过多,这里暂时不做详细解释。
网友评论