Event Loop事件循环执行过程

2019.07/JavaScript

JavaScript语言是单线程的,同步任务在主线程上执行,形成一个执行栈(JS stack)。

异步任务,包括setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O、UI rendering等,也被称作宏任务(macrotask)。

主线程之外存在一个先进先出的任务队列(Task queue),异步任务的运行结果会在任务队列中放置对应事件(callback)。

一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列的事件并执行之。主线程从任务队列中读取事件的过程是不断重复循环的,这种运行机制便称为事件循环Event Loop。

JavaScript中还存在另外的一类异步任务,包括process.nextTick、Promise、MutationObserver,以及已经废弃的Object.observe,被称做微任务(microtask),它们的回调函数会被放进微任务队列(Microtask queue)。

执行栈中的当前任务执行结束后,会立即检查并处理微任务队列,处理过程中产生的微任务也会被立即处理掉,而不用等下次事件循环。

通俗来讲,macrotask的规则是等下一班车,microtask的规则是挂在当前车尾,而且允许现做现卖。

下面是JavaScript事件循环Event Loop执行示例:

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')
Task queue
Run script
setTimeout callback
Microtask queue
Promise then
Promise then
JS stack
Log
script start
script end
promise1
promise2
setTimeout
点击按钮切换执行步骤

具体执行过程大致如下:

  1. JS线程启动,创建事件循环
  2. 当前同步代码Run script为任务队列的第一项
  3. script加入执行栈
  4. 执行console.log('script start'),输出日志“script start”
  5. 执行setTimeout...,其回调函数被插入任务队列
  6. 执行Promise.resolve()...,其回调函数console.log('promise1')被插入微任务队列
  7. 执行console.log('script end'),输出日志“script end”
  8. script出栈,执行栈为空,立即检查并处理微任务队列
  9. 微任务console.log('promise1')入执行栈执行,输出日志“promise1”
  10. 处理过程中产生新的微任务console.log('promise2')立即入执行栈处理,输出日志“promise2”
  11. Run script事件结束,开始检查任务队列
  12. console.log('setTimeout')入执行栈执行,输出日志“setTimeout”
  13. 进行新一轮事件循环

所以这段代码Log日志的出现顺序为:script start、script end、promise1、promise2、setTimeout。

参考:
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
http://www.ayqy.net/blog/javascript-macrotask-vs-microtask/