事件循环 Event Loop( 二 )


具体描述一下事件队列的过程 , 整体 script 作为第一个宏任务 , 开始第一轮循环 , 执行完所有同步任务 , 遇到异步任务交给异步处理线程 , 同步任务执行完之后执行所有为任务队列的任务 , 然后渲染 。 之后进入第二轮循环 , 去一个宏任务 , 执行完毕之后执行所有微任务 , 然后进入第三轮循环 , 依次执行下去 。 所以我们注意 process.nextTick 和 Promise.then 会在当前循环执行 , 整体 script 是第一个宏任务 , 就能处理大部分问题了 。 比如下面这题求输出:
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')})})//1 , 7 , 6 , 8 , 2 , 4 , 3 , 5 , 9 , 11 , 10 , 12从逻辑上来看 , 浏览器倾向于尽可能快地执行完微任务 , 当全局任务(其实是全局函数中的同步任务)执行完之后 , 会立即执行微任务队列 , 即使微任务队列执行完了 , 在每次执行完一个宏任务之后都会检查微任务队列 , 如果就微任务就一直执行到微任务队列为空才会执行宏任务 。
console.log('script start');// 微任务Promise.resolve().then(() => {console.log('p 1');});// 宏任务setTimeout(() => {console.log('setTimeout');}, 0);var s = new Date();while(new Date() - s < 50); // 阻塞50ms// 微任务Promise.resolve().then(() => {console.log('p 2');});console.log('script ent');/*** output ***/// one macro taskscript startscript ent// all micro tasksp 1p 2// one macro task againsetTimeoutNodeJs 的 Event Loop
在 Event Loop 之前会先做这些工作:
1. 初始化 Event Loop
2. 执行主代码 。 这里同样 , 遇到异步处理 , 就会分配给对应的队列 。 直到主代码执行完毕 。
3. 执行主代码中出现的所有微任务:先执行完所有nextTick() , 然后在执行其它所有微任务 。
4. 开始 Event Loop
Event Loop 分为 6 个阶段:
1. timers : 这个阶段执行 setTimeout() 和 setInterval() 设定的回调 。
2. pending callbacks : 上一轮循环中有少数的 I/O callback 会被延迟到这一轮的这一阶段执行 。
3. idle , prepare : 仅内部使用 。
4. poll : 执行 I/O callback, 在适当的条件下会阻塞在这个阶段
5. check : 执行 setImmediate() 设定的回调 。
6. close callbacks : 执行比如 socket.on('close', ...) 的回调 。
每个阶段执行完毕后 , 都会执行所有微任务(先 nextTick, 后其它) , 然后再进入下一个阶段 。
setTimeout把 setTimeout 单独拿出来说是因为它有几点比较特别的地方 。
1. setTimeout 是异步的 , 它会有主线程交给事件触发线程 , 然后放到宏队列中去 。 并且 HTML5 标准规定了 delay ( setTimeout )的第二个参数至少为 4ms, 即使你写 0。
setTimeout(function () {console.log(1);}, 0);console.log(2);//output2

  1. setTimeout 的 delay 只能表示它被事件触发程序放到任务队列中的时间 , 如果此时任务队列前面没有任务 , 执行栈也为空 , 那么回调函数会被立即执行 , 但是如果任务队列前面还有未执行任务或者执行栈中不为空 , 则需要继续等待 。
var starttime = new Date().getTime()console.log("start " + starttime);setTimeout(function () {var endtime = new Date().getTime()console.log("end " + endtime);console.log("timediff " + (endtime - starttime));}, 1000);while (new Date().getTime() - starttime < 3000) {continue;}//outputstart 1556197854352end 1556197857353timediff 3001参考文章
  1. JavaScript 异步、栈、事件循环、任务队列
  2. 一次弄懂Event Loop
  3. 深入解析 EventLoop 和浏览器渲染、帧动画、空闲回调的关系
  4. 深入探究 eventloop 与浏览器渲染的时序问题
  5. requestAnimationFrame是一个宏任务么
  6. 浏览器与Node的事件循环(Event Loop)有何区别?
  7. 浏览器和Node 事件循环的区别
作者:Clloz
【事件循环 Event Loop】出处: