事件循环(Event Loop)

72 阅读2分钟

JS是一个单线程,JS任务分为同步任务和异步任务

同步任务在主线程中按顺序执行,异步任务不进入主线程,而是进入任务队列中,只有异步任务通知主线程可以执行了,该任务才会进入主线程执行。

异步任务又分为宏任务、微任务

常见宏任务

  • script
  • setTimeout、setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O(Node.js)

setImmediate方法则是在当前"任务队列"的尾部添加事件

常见微任务

  • Promise
  • process.nextTick(Node.js)

process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数

事件执行顺序

  1. 页面执行JS脚本,所有同步任务形成一个执行栈
  2. 主线程外还有一个任务队列,异步任务有了运行结果,就会在任务队列中放入一个事件
  3. 执行栈中所有任务完成后,就会去任务队列中读取是否有可执行任务
  4. 先执行完队列中所有微任务
  5. 执行下一个宏任务
  6. 在执行完微任务
  7. 重复5~6

案例

async function async1() {
    console.log('async1 start')
    await async2()
    console.log('async1 end')
}
async function async2() {
    console.log('async2')
}
console.log('script start')
setTimeout(function () {
    console.log('settimeout')
})
async1()
new Promise(function (resolve) {
    console.log('promise1')
    resolve()
}).then(function () {
    console.log('promise2')
})
console.log('script end')

输出

script start、async1 start、async2、promise1、script end、async1 end、promise2、settimeout

分析

  1. 执行整段代码,遇到 console.log(‘script start’) 直接打印结果,输出 script start
  2. 遇到定时器了,它是宏任务,先放着不执行
  3. 遇到 async1(),执行 async1 函数,先打印 async1 start,下面遇到await怎么办?先执行 async2,打印 async2,然后阻塞下面代码(即加入微任务列表),跳出去执行同步代码
  4. 跳到 new Promise 这里,直接执行,打印 promise1,下面遇到 .then(),它是微任务,放到微任务列表等待执行
  5. 最后一行直接打印 script end,现在同步代码执行完了,开始执行微任务,即 await 下面的代码,打印 async1 end
  6. 继续执行下一个微任务,即执行 then 的回调,打印 promise2
  7. 上一个宏任务所有事都做完了,开始下一个宏任务,就是定时器,打印 settimeout