一文带你读懂浏览器的事件循环机制

490 阅读4分钟

浏览器事件循环

  1. js是单线程的 非阻塞的
    1. 如果是多线程 会面临很多问题 比如一个DOM元素被同时操作带来的问题 (H5提出Web Worker标准 有很多限制 受主线程的控制 是主线程的子线程
    2. 通过event Loop实现非阻塞
  2. 浏览器的事件循环
    1. 执行栈事件队列
    2. 宏任务微任务

浏览器的事件循环

执行栈和事件队列

preview

事件队列:异步代码的执行 并不会等待它返回的结果 而是直接将这个事件挂起 继续执行栈中的其他任务 当异步事件返回结果 将它放到事件队列中(被放入的事件不会立即执行回调 而是继续等待当前执行栈中的任务都执行完毕)主线程空闲 就回去事件队列查找是否有任务 如果有 则取出排在前面的事件对应的回调到执行栈中 然后执行其同步代码

2402010291-b9a57af85eebdab7

宏任务和微任务

Q:为什么需要有微任务?

A: 为了准确控制事件被添加到事件队列中的位置并加快执行高优先级的事件 (如果没有微任务 那么像IO事件和浏览器渲染事件就会被任意添加到事件队列中 难以控制)

宏任务

  • script
  • setTimeOut()
  • setInterval()
  • postMessage
  • I/O
  • setImmediate
  • UI RENDER

微任务

  • Promise(async await)
  • MutationObserver(html5回调)
  • process.nextTick(和普通微任务有区别 在微任务执行之前执行)

运行机制

异步任务的返回结果会被放到任务队列中 根据事件类型决定放到微任务还是宏任务队列

当前执行栈为空时 会先查看微任务队列是否有待执行的事件

  • 有 依次执行微任务队列的回调直到队列为空 然后取出宏任务队列最前面的事件 将其回调添加到执行栈
  • 无 取出宏队列最前面事件对应的回调并放入执行栈中

当前执行栈执行完毕后时会立刻处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行

1100815376-96173ef6295d6c1f

总结:先执行宏任务 然后执行该宏任务产生的微任务 若微任务执行中产生了其他任务(微任务则继续执行微任务 宏任务则放入下一轮宏任务中) 微任务执行完毕 再回到宏任务中进行下一轮循环

小练

console.log('1')
new Promise((resolve, reject) => {
  console.log('p2');
  resolve(3)
}).then((result) => {
  console.log(result);
  setTimeout(() => {
    console.log('pro time')
  }, 0)
  console.log('after pro time')
}).then(() => {
  console.log('second then')
})
setTimeout(() => {
  console.log('t1')
}, 0)
console.log('end')

第一轮事件循环

  1. 全局代码压入执行栈 执行console.log('1') 然后出栈
  2. new Promise()入执行栈 执行console.log(p2) 并将then()放入微任务队列 new Promise()出栈
  3. setTimeout()入执行栈 将对应事件放入宏任务队列 setTimeout出栈
  4. console.log('end') 入栈 执行console.log('end') 然后出栈
  5. 至此第一轮事件循环结束 此时微任务有promise.then() 宏任务有setTimeout() 输出1-p2-3

第二轮事件循环

  1. 取出微任务队列第一个事件的回调至任务栈 及第一个then 执行console.log(result) 遇到setTimeout 将其翻入宏任务 继续执行下面的代码 即console.log('after pro time') 遇到then 属于微任务 则继续执行 console.log('second then') 微任务队列清空 前往宏任务队列
  2. 取出宏任务队列第一个事件并将回调放入执行栈 即console.log(t1)
  3. 至此第二轮事件循环结束 宏任务队列还有一个setTimeout(刚才promise产生的) 微任务无事件 输出"after pro time" - "second then" - "t1"

第三轮事件循环

  1. 检查微任务队列 无待处理事件 前往宏任务队列
  2. 取出宏任务队列第一个事件 执行console.log('pro time')
  3. 宏任务和微任务队列均清空 第三轮事件循环结束 输出 "pro time"

至此 所有事件执行完毕 输出依次为:"1" - "p2" - "end" - 3 - "after pro time" - "second then" - "t1" - "pro time"

相信看到这里,大家对于事件循环应该有了深刻的认识,本文错误之处,请各位在评论区指出

文章参考JS中的事件循环机制