JavaScript事件循环:任务队列与微任务队列的执行顺序详解

JavaScript事件循环:任务队列与微任务队列的执行顺序详解

本文旨在深入剖析JavaScript事件循环机制中任务队列(Task Queue)与微任务队列(Job Queue,也称Microtask Queue)的执行优先级和相互影响。通过具体代码示例,详细解释了setTimeout、promise异步操作在事件循环中的调度方式,以及微任务如何优先于任务队列中的任务执行,从而帮助开发者更深入地理解JavaScript的异步编程模型。

JavaScript事件循环机制

JavaScript 是一种单线程语言,这意味着它一次只能执行一个任务。为了处理异步操作,JavaScript 引入了事件循环机制。事件循环不断地检查调用(Call Stack)是否为空,如果为空,则从任务队列(Task Queue)中取出第一个任务放入调用栈中执行。执行完毕后,再次检查调用栈,如此循环。

任务队列(Task Queue)与微任务队列(Job Queue)

任务队列和微任务队列都是用于存放待执行任务的队列,但它们的优先级不同。

  • 任务队列(Task Queue):也称为宏任务队列,用于存放诸如 setTimeout、setInterval、I/O 操作等异步任务的回调函数
  • 微任务队列(Job Queue):也称为微任务队列,用于存放诸如 Promise.then、MutationObserver 等异步任务的回调函数。

执行顺序:

立即学习Java免费学习笔记(深入)”;

  1. 执行全局同步代码。
  2. 当调用栈为空时,检查微任务队列。
  3. 如果微任务队列不为空,则依次执行微任务队列中的所有微任务。
  4. 当微任务队列为空时,从任务队列中取出一个任务放入调用栈中执行。
  5. 重复步骤 2-4。

关键在于,每次从任务队列中取出一个任务执行后,都会立即清空微任务队列,然后再取下一个任务。 这意味着微任务的优先级高于任务队列中的任务。

代码示例分析

以下面的代码为例:

setTimeout(() => {   console.log('1'); }, 0);  Promise.resolve('2').then(console.log);  console.log('3');

这段代码的执行过程如下:

  1. setTimeout 被添加到调用栈,然后被推入 WebAPI,并从调用栈中弹出。setTimeout 的回调函数被放入任务队列。
  2. Promise.resolve(‘2’).then(console.log) 被添加到调用栈。Promise.resolve 创建一个已解决的 Promise,其 then 方法的回调函数被放入微任务队列。
  3. console.log(‘3’) 被添加到调用栈并执行,输出 3。
  4. 此时调用栈为空,事件循环检查微任务队列,发现 Promise.then 的回调函数,将其放入调用栈并执行,输出 2。
  5. 微任务队列为空,事件循环从任务队列中取出 setTimeout 的回调函数,将其放入调用栈并执行,输出 1。

因此,最终的输出结果是 3 2 1。

错误示例分析

下面这段代码容易产生误解:

setTimeout(() => {   console.log('1'); }, 0);  Promise.resolve(setTimeout(() => {   console.log('2'); }, 0));  console.log('3');

这段代码的输出结果是 3 1,然后一段时间后输出 2。

原因分析:

这段代码中,Promise.resolve 的参数是 setTimeout 的返回值,也就是 setTimeout 的 ID(一个数字)。Promise.resolve 创建一个已解决的 Promise,其值为 setTimeout 的 ID。因此,Promise.then 的回调函数并没有被执行,而 setTimeout 的回调函数仍然被放入任务队列,按照 setTimeout 的执行顺序执行。

正确的理解是:setTimeout 的调用是同步的,它将一个回调函数注册到 WebAPI,并返回一个 timer ID。这个 timer ID 被 Promise.resolve 包裹,并立即resolve。console.log(‘2’) 最终会在 setTimeout 的回调函数中执行,它与 Promise 的状态无关。

总结与注意事项

  • 微任务队列的优先级高于任务队列。
  • 每次从任务队列中取出一个任务执行后,都会立即清空微任务队列。
  • Promise.resolve(setTimeout(…)) 这种写法容易产生误解,应该避免。
  • 理解事件循环机制对于编写高性能的 JavaScript 代码至关重要。

通过深入理解 JavaScript 的事件循环机制,我们可以更好地控制异步代码的执行顺序,避免出现意外的行为,并编写出更加高效和可靠的 JavaScript 应用。

© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享