JS 事件循环--宏任务与微任务

Event Loop

Posted by Z1hgq on October 23, 2019

“”


Event Loop

JavaScript是一门单线程的脚本语言,在一行代码执行的过程中,必然不会存在同时执行的另一行代码,就像使用alert()以后进行疯狂console.log,如果没有关闭弹框,控制台是不会显示出一条log信息的。

js的主线程只负责维护任务队列,并通过事件循环机制(Event Loop)按照顺序将需要执行的任务放进栈中。

  • 同步和异步任务分别进入不同的执行”场所”,同步的进入主线程,异步的进入Event Table并注册函数
  • 当指定的事情完成时,Event Table会将这个函数移入Event Queue。
  • 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
  • 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

宏任务与微任务

在Js中,有两类任务队列:宏任务队列(macro tasks)和微任务队列(micro tasks)。宏任务队列可以有多个,微任务队列只有一个。

宏任务:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering.

微任务:process.nextTick, Promise, Object.observer, MutationObserver.

浏览器Event Loop

  1. 取一个宏任务来执行。执行完毕后,下一步。
  2. 取一个微任务来执行,执行完毕后,再取一个微任务来执行。直到微任务队列为空,执行下一步。
  3. 更新UI渲染

每执行下一次宏任务之前重新渲染。

NodeJs Event Loop

  1. 初始化 Event Loop
  2. 执行您的主代码。这里同样,遇到异步处理,就会分配给对应的队列。直到主代码执行完毕。
  3. 执行主代码中出现的所有微任务:先执行完所有nextTick(),然后在执行其它所有微任务。
  4. 开始 Event Loop

NodeJs 的 Event Loop 分为六个阶段:timers、pending callbacks、idle\prepare、poll、check、close callbacks。

┌───────────────────────────┐

┌─│———–timers———-│

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │—- pending callbacks—–│

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

│ │——-idle, prepare——-│

│ └─────────────┬─────────────┘ ┌───────────────┐

│ ┌─────────────┴─────────────┐ │ incoming: │

│ │———–poll————│<─────┤ connections, │

│ └─────────────┬─────────────┘ │ data, etc. │

│ ┌─────────────┴─────────────┐ └───────────────┘

│ │———–check———–│

│ └─────────────┬─────────────┘

│ ┌─────────────┴─────────────┐

└──┤——close callbacks——│

└───────────────────────────┘

  • timers: 这个阶段执行setTimeout()和setInterval()设定的回调。
  • pending callbacks: 上一轮循环中有少数的 I/O callback 会被延迟到这一轮的这一阶段执行。
  • idle, prepare: 仅内部使用。
  • poll: 执行 I/O callback,在适当的条件下会阻塞在这个阶段
  • check: 执行setImmediate()设定的回调。
  • close callbacks: 执行比如socket.on(‘close’, …)的回调。

示例分析

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

在上面的代码中,node环境下的输出顺序为7\6\8\2\4\9\11\3\10\5\12,在浏览器中的输出顺序为7\8\2\4\5\9\11\12,在浏览器中去除了Process.nextTick()这个方法。

总结

参考文献

0. JavaScript 异步、栈、事件循环、任务队列 1. JS事件循环机制(event loop)之宏任务/微任务