一段代码读懂浏览器的事件循环

126 阅读3分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

前言

“八月的色彩是用金子铸就的,明亮而珍贵;八月的色彩是用阳光酿造的,芬芳而灿烂。”

未来的日子,愿你把自己调至最佳状态,缓缓努力,慢慢变好 Y(^o^)Y

事件循环

我们都只 JS 是单线程的,代码的执行都是‘自上而下‘依次运行的

function test() {
  console.log(2)
}
console.log(1)
test();

上面这段简单的代码,都能很轻松的看出它的打印结果。

但是如果我们给它加入定时器和异步任务呢

setTimeout(() => {
  console.log('setTimeout1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
  Promise.resolve().then(() => {
    console.log('promise3')
  })
})
setTimeout(() => {
  console.log('setTimeout2')
  Promise.resolve().then(() => {
    console.log('promise4')
  })
})
Promise.resolve().then(() => {
  console.log('promise1')
})
function test() {
  console.log('test1')
}

test();

上面我们提到 js 是单线程的,浏览器在运行代码的时候,遇到定时器或者异步任务的时候,不会去一直等到这些执行结束,而是会通过浏览器的事件循环event loop)机制去有序的执行。

事件循环有一个或多个任务队列。一个任务队列是一组任务

任务队列是集合,而不是队列,因为事件循环处理模型的第一步是从所选队列中获取第一个可运行的任务,而不是将第一个任务出队

微任务队列不是任务队列

每个事件循环都有一个当前正在运行的任务,它要么是一个任务,要么是空的。最初,这是空的。它用于处理重入。

Each event loop has a currently running task, which is either a task or null. Initially, this is null. It is used to handle reentrancy.

每个事件循环都有一个微任务队列,它是一个微任务队列,最初是空的。微任务是指通过微任务算法队列创建的任务。

Each event loop has a microtask queue, which is a queue of microtasks, initially empty. A microtask is a colloquial way of referring to a task that was created via the queue a microtask algorithm.

每个事件循环都有一个执行微任务检查点布尔值,它最初是假的。它用于防止执行微任务检查点算法的可重入调用。

Each event loop has a performing a microtask checkpoint boolean, which is initially false. It is used to prevent reentrant invocation of the perform a microtask checkpoint algorithm.

事件循环中的任务分为宏任务(Macrotasks)和微任务(Microtasks

宏任务的范围是比较广的,如点击事件(click

  • script(完整的代码)
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI rendering

微任务,常见的主要是promise事件

  • process.nextTick
  • promises
  • Object.observe
  • MutationObserver

事件循环的任务执行机制

  1. 浏览器首先从 Macrotasks 队列(task queue)中取出一个宏任务执行
  2. 宏任务执行执行完毕,然后取出所有的 Microtasks 执行

当某个宏任务(Macrotasks)执行完后,会查看是否有微任务队列(Microtasks)。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。宏任务执行后,再次读取微任务队列里的任务,依次类推

代码分析

现在我们回过头来看一下上面的代码执行结果:

test1
promise1
setTimeout1
promise2
promise3
setTimeout2
promise4

宏任务有三个: scriptsetTimeout1setTimeout2

  1. 执行全部的script 代码(宏任务),中间包含一个微任务(promise1),第一次循环得到的结果是 test1promise1
  2. 执行宏任务(setTimeout1),发现里面包含了两个微任务(promise2promise3),第二次循环得到结果是 setTimeout1promise2promise3
  3. 执行宏任务(setTimeout2),里面包含一个微任务(promise4),第三次循环得到结果是 setTimeout2promise4

这就是完成事件循环过程,总之,浏览器端,Microtask 在事件循环的 Macrotask 执行完之后执行

结语

如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。

文章如有错误之处,希望在评论区指正🙏🙏。

附:Event loops