js 中的 EventLoop
起始
EventLoop 是什么
JavaScript有一个基于事件循环(EventLoop)的并发模型,事件循环负责执行代码、收集和处理事件以及执行队列中的子工作。 > 浏览器和NodeJS基于不同的技术实现了各自的Event Loop。
事件循环
之所以称之为 事件循环,是因为它常常依照相似如下的形式来被实现:
while (queue.waitForMessage()) { queue.processNextMessage();}
queue.waitForMessage() 会同步地期待音讯达到(如果以后没有任何音讯期待被解决)。
事件
同步工作和异步工作
Javascript单线程工作被分为同步工作和异步工作,同步工作会在调用栈中依照程序期待主线程顺次执行,异步工作会在异步工作有了后果后,将注册的回调函数放入工作队列中期待主线程闲暇的时候(调用栈被清空),被读取到栈内期待主线程的执行。
从图中能够看出, js 的指向都是会先指向同步代码,碰到异步代码都是存入队列中, 等同步代码执行结束之后再依照肯定的规定执行
异步工作的分类
宏工作
macrotask,也叫tasks。 一些异步工作的回调会顺次进入macro task queue,期待后续被调用,这些异步工作包含
- setTimeout
- setInterval
- setImmediate (Node独有)
- requestAnimationFrame (浏览器独有)
- I/O
- UI rendering (浏览器独有)
微工作
microtask,也叫jobs。 另一些异步工作的回调会顺次进入micro task queue,期待后续被调用,这些异步工作包含:
- process.nextTick (Node独有)
- Promise
- Object.observe
- MutationObserver
这里只针对浏览器和NodeJS
运行
运行原理:
- 运行同步工作, 微工作退出队列, 期待同步工作执行结束
- 查看是否有微工作, 若有则执行, 依照先进先出的规定进行
- 微工作完结后执行宏工作, 执行完每一个宏工作之后都会再次进入第 2 步流程
- 微工作和宏工作都执行结束
例子1:
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 startscript endpromise1promise2setTimeout
剖析一下执行的过程:
- 执行了同步代码
console.log('script start');
和console.log('script end');
- 执行微工作
Promise
,打印promise
- 执行宏工作
setTimeout
- 清空队列和栈堆
例子 2:
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);
后果:
1475236
执行过程:
- 首先天然是执行同步代码: 几个 console, 对于
new Promise
须要留神的是, 他new
的时候, 并不是异步的,回调函数才是异步工作, 所以打印的是: 1,4,7 - 执行微工作, promise 里的几个 then 回调函数, 所以打印 5
- 微工作临时执行结束, 执行宏工作, setTimeout, 打印 2, 这个时候又呈现了一个微工作退出了微工作的队列
- 一个宏工作执行结束了, 发现了新的同步工作和微工作,开始执行, 打印 3, 微工作执行结束
- 执行下一个宏工作, 打印 6
- 全副执行结束, 打印程序是: 1,4,7,5,2,3,6
在执行微队列microtask queue中工作的时候,如果又产生了microtask,那么会持续增加到队列的开端,也会在这个周期执行,直到microtask queue为空进行。 当然如果你在microtask中一直的产生microtask,那么其余宏工作macrotask就无奈执行了,然而这个操作也不是有限的,拿NodeJS中的微工作process.nextTick()来说,它的下限是1000个;
node 中的 EventLoop
--- 援用自其它博客Node
中的Event Loop
是基于libuv
实现的,而libuv
是Node
的新跨平台形象层,libuv应用异步,事件驱动的编程形式,外围是提供i/o
的事件循环和异步回调。libuv的API
蕴含有工夫,非阻塞的网络,异步文件操作,子过程等等。Event Loop
就是在libuv
中实现的。
node 中的宏队列
宏队列的回调工作有如下 6 个阶段
各个阶段执行的工作如下:
- timers阶段:这个阶段执行setTimeout和setInterval预约的callback
- I/O callback阶段:执行除了close事件的callbacks、被timers设定的callbacks、setImmediate()设定的callbacks这些之外的callbacks
- idle, prepare阶段:仅node外部应用
- poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里
- check阶段:执行setImmediate()设定的callbacks
- close callbacks阶段:执行socket.on('close', ....)这些callbacks
NodeJS中宏队列次要有4个
由下面的介绍能够看到,回调事件次要位于4个macrotask queue中:
- Timers Queue
- IO Callbacks Queue
- Check Queue
- Close Callbacks Queue
这4个都属于宏队列,然而在浏览器中,能够认为只有一个宏队列,所有的macrotask都会被加到这一个宏队列中,然而在NodeJS中,不同的macrotask会被搁置在不同的宏队列中。
NodeJS中微队列次要有2个:
- Next Tick Queue:是搁置process.nextTick(callback)的回调工作的
- Other Micro Queue:搁置其余microtask,比方Promise等
大体解释一下NodeJS的Event Loop过程:
- 执行全局Script的同步代码
- 执行microtask微工作,先执行所有Next Tick Queue中的所有工作,再执行Other Microtask Queue中的所有工作
- 开始执行macrotask宏工作,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有工作,留神,这里是所有每个阶段宏工作队列的所有工作,在浏览器的Event Loop中是只取宏队列的第一个工作进去执行,每一个阶段的macrotask工作执行结束后,开始执行微工作,也就是步骤2
- Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue ......
- 这就是Node的Event Loop
总体来说运行机制是和浏览器外面差不多的 也是先同步代码, 再一步微工作,宏工作,微工作...这样
惟一不同点就是宏工作是一系列动作,在第一个动作结束后就会去查看微工作
援用文章:
https://segmentfault.com/a/11...
https://developer.mozilla.org...
https://zhuanlan.zhihu.com/p/...