我在学习浏览器和NodeJS的Event Loop时看了大量的文章,那些文章都写的很好,可是每每是每篇文章有那么几个关键的点,不少篇文章凑在一块儿综合来看,才能够对这些概念有较为深刻的理解。html
因而,我在看了大量文章以后,想要写这么一篇博客,不采用官方的描述,结合本身的理解以及示例代码,用最通俗的语言表达出来。但愿你们能够经过这篇文章,了解到Event Loop究竟是一种什么机制,浏览器和NodeJS的Event Loop又有什么区别。若是在文中出现书写错误的地方,欢迎你们留言一块儿探讨。html5
(PS:说到Event Loop确定会提到Promise,我根据Promise A+规范本身实现了一个简易Promise库,源码放到Github上,你们有须要的能够当作参考,后续我也会也写一篇博客来说Promise,若是对你有用,就请给个Star吧~)node
event loop是一个执行模型,在不一样的地方有不一样的实现。浏览器和NodeJS基于不一样的技术实现了各自的Event Loop。git
宏队列,macrotask,也叫tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:github
微队列,microtask,也叫jobs。 另外一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:web
(注:这里只针对浏览器和NodeJS)segmentfault
咱们先来看一张图,再看完这篇文章后,请返回来再仔细看一下这张图,相信你会有更深的理解。api
这张图将浏览器的Event Loop完整的描述了出来,我来说执行一个JavaScript代码的具体流程:promise
能够看到,这就是浏览器的事件循环Event Loop浏览器
这里概括3个重点:
好了,概念性的东西就这么多,来看几个示例代码,测试一下你是否掌握了:
console.log(1); setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }); new Promise((resolve, reject) => { console.log(4) resolve(5) }).then((data) => { console.log(data); }) setTimeout(() => { console.log(6); }) console.log(7);
这里结果会是什么呢?运用上面了解到的知识,先本身作一下试试看。
// 正确答案 1 4 7 5 2 3 6
你答对了吗?
咱们来分析一下整个流程:
Step 1
console.log(1)
Stack Queue: [console]
Macrotask Queue: []
Microtask Queue: []
打印结果:
1
Step 2
setTimeout(() => { // 这个回调函数叫作callback1,setTimeout属于macrotask,因此放到macrotask queue中 console.log(2); Promise.resolve().then(() => { console.log(3) }); });
Stack Queue: [setTimeout]
Macrotask Queue: [callback1]
Microtask Queue: []
打印结果:
1
Step 3
new Promise((resolve, reject) => { // 注意,这里是同步执行的,若是不太清楚,能够去看一下我开头本身实现的promise啦~~ console.log(4) resolve(5) }).then((data) => { // 这个回调函数叫作callback2,promise属于microtask,因此放到microtask queue中 console.log(data); })
Stack Queue: [promise]
Macrotask Queue: [callback1]
Microtask Queue: [callback2]
打印结果:
1
4
Step 5
setTimeout(() => { // 这个回调函数叫作callback3,setTimeout属于macrotask,因此放到macrotask queue中 console.log(6); })
Stack Queue: [setTimeout]
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
打印结果:
1
4
Step 6
console.log(7)
Stack Queue: [console]
Macrotask Queue: [callback1, callback3]
Microtask Queue: [callback2]
打印结果:
1
4
7
Step 7
console.log(data) // 这里data是Promise的决议值5
Stack Queue: [callback2]
Macrotask Queue: [callback1, callback3]
Microtask Queue: []
打印结果:
1
4
7
5
Step 8
console.log(2)
Stack Queue: [callback1]
Macrotask Queue: [callback3]
Microtask Queue: []
打印结果:
1
4
7
5
2
可是,执行callback1的时候又遇到了另外一个Promise,Promise异步执行完后在microtask queue中又注册了一个callback4回调函数
Step 9
Promise.resolve().then(() => { // 这个回调函数叫作callback4,promise属于microtask,因此放到microtask queue中 console.log(3) });
Stack Queue: [promise]
Macrotask v: [callback3]
Microtask Queue: [callback4]
打印结果:
1
4
7
5
2
Step 10
console.log(3)
Stack Queue: [callback4]
Macrotask Queue: [callback3]
Microtask Queue: []
打印结果:
1
4
7
5
2
3
Step 11
console.log(6)
Stack Queue: [callback3]
Macrotask Queue: []
Microtask Queue: []
打印结果:
1
4
7
5
2
3
6
Stack Queue: []
Macrotask Queue: []
Microtask Queue: []
最终打印结果:
1
4
7
5
2
3
6
由于是第一个例子,因此这里分析的比较详细,你们仔细看一下,接下来咱们再来一个例子:
console.log(1); setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3) }); }); new Promise((resolve, reject) => { console.log(4) resolve(5) }).then((data) => { console.log(data); Promise.resolve().then(() => { console.log(6) }).then(() => { console.log(7) setTimeout(() => { console.log(8) }, 0); }); }) setTimeout(() => { console.log(9); }) console.log(10);
最终输出结果是什么呢?参考前面的例子,好好想想......
// 正确答案 1 4 10 5 6 7 2 3 9 8
相信你们都答对了,这里的关键在前面已经提过:
在执行微队列microtask queue中任务的时候,若是又产生了microtask,那么会继续添加到队列的末尾,也会在这个周期执行,直到microtask queue为空中止。
注:固然若是你在microtask中不断的产生microtask,那么其余宏任务macrotask就没法执行了,可是这个操做也不是无限的,拿NodeJS中的微任务process.nextTick()来讲,它的上限是1000个,后面咱们会讲到。
浏览器的Event Loop就说到这里,下面咱们看一下NodeJS中的Event Loop,它更复杂一些,机制也不太同样。
先来看一张libuv的结构图:
NodeJS的Event Loop中,执行宏队列的回调任务有6个阶段,以下图:
各个阶段执行的任务以下:
NodeJS中宏队列主要有4个
由上面的介绍能够看到,回调事件主要位于4个macrotask queue中:
这4个都属于宏队列,可是在浏览器中,能够认为只有一个宏队列,全部的macrotask都会被加到这一个宏队列中,可是在NodeJS中,不一样的macrotask会被放置在不一样的宏队列中。
NodeJS中微队列主要有2个:
在浏览器中,也能够认为只有一个微队列,全部的microtask都会被加到这一个微队列中,可是在NodeJS中,不一样的microtask会被放置在不一样的微队列中。
具体能够经过下图加深一下理解:
大致解释一下NodeJS的Event Loop过程:
关于NodeJS的macrotask queue和microtask queue,我画了两张图,你们做为参考:
好啦,概念理解了咱们经过几个例子来实战一下:
第一个例子
console.log('start'); setTimeout(() => { // callback1 console.log(111); setTimeout(() => { // callback2 console.log(222); }, 0); setImmediate(() => { // callback3 console.log(333); }) process.nextTick(() => { // callback4 console.log(444); }) }, 0); setImmediate(() => { // callback5 console.log(555); process.nextTick(() => { // callback6 console.log(666); }) }) setTimeout(() => { // callback7 console.log(777); process.nextTick(() => { // callback8 console.log(888); }) }, 0); process.nextTick(() => { // callback9 console.log(999); }) console.log('end');
请运用前面学到的知识,仔细分析一下......
// 正确答案 start end 999 111 777 444 888 555 333 666 222
更新 2018.9.20
上面这段代码你执行的结果可能会有多种状况,缘由解释以下。
综上,这个例子是不太好的,setTimeout(fn, 0)和setImmediate(fn)若是想要保证结果惟一,就放在一个IO Callback中吧,上面那段代码能够把全部它俩同步执行的代码都放在一个IO Callback中,结果就惟一了。
更新结束
你答对了吗?咱们来一块儿分析一下:
宏队列
Timers Queue: [callback1, callback7]
Check Queue: [callback5]
IO Callback Queue: []
Close Callback Queue: []
微队列
Next Tick Queue: [callback9]
Other Microtask Queue: []
打印结果
start
end
宏队列
Timers Queue: [callback1, callback7]
Check Queue: [callback5]
IO Callback Queue: []
Close Callback Queue: []
微队列
Next Tick Queue: []
Other Microtask Queue: []
打印结果
start
end
999
宏队列
Timers Queue: [callback2]
Check Queue: [callback5, callback3]
IO Callback Queue: []
Close Callback Queue: []
微队列
Next Tick Queue: [callback4, callback8]
Other Microtask Queue: []
打印结果
start
end
999
111
777
宏队列
Timers Queue: [callback2]
Check Queue: [callback5, callback3]
IO Callback Queue: []
Close Callback Queue: []
微队列
Next Tick Queue: []
Other Microtask Queue: []
打印结果
start
end
999
111
777
444
888
宏队列
Timers Queue: [callback2]
Check Queue: []
IO Callback Queue: []
Close Callback Queue: []
微队列
Next Tick Queue: [callback6]
Other Microtask Queue: []
打印结果
start
end
999
111
777
444
888
555
333
宏队列
Timers Queue: [callback2]
Check Queue: []
IO Callback Queue: []
Close Callback Queue: []
微队列
Next Tick Queue: [callback6]
Other Microtask Queue: []
打印结果
start
end
999
111
777
444
888
555
333
宏队列
Timers Queue: []
Check Queue: []
IO Callback Queue: []
Close Callback Queue: []
微队列
Next Tick Queue: [callback6]
Other Microtask Queue: []
最终结果
start
end
999
111
777
444
888
555
333
666
222
以上就是这道题目的详细分析,若是没有明白,必定要多看几回。
下面引入Promise再来看一个例子:
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') }) }) new Promise(function(resolve) { console.log('7'); resolve(); }).then(function() { console.log('8') }) process.nextTick(function() { console.log('6'); }) setTimeout(function() { console.log('9'); process.nextTick(function() { console.log('10'); }) new Promise(function(resolve) { console.log('11'); resolve(); }).then(function() { console.log('12') }) })
你们仔细分析,相比于上一个例子,这里因为存在Promise,因此Other Microtask Queue中也会有回调任务的存在,执行到微任务阶段时,先执行Next Tick Queue中的全部任务,再执行Other Microtask Queue中的全部任务,而后才会进入下一个阶段的宏任务。明白了这一点,相信你们均可以分析出来,下面直接给出正确答案,若有疑问,欢迎留言和我讨论。
// 正确答案 1 7 6 8 2 4 9 11 3 10 5 12
二者的执行顺序要根据当前的执行环境才能肯定:
不要混淆nodejs和浏览器中的event loop
node中的Event模块
Promises, process.nextTick And setImmediate
浏览器和Node不一样的事件循环
Tasks, microtasks, queues and schedules
理解事件循环浅析