返回

用宏任务和微任务理解异步回调的执行时机差异

前端

在现代Web开发中,异步编程无处不在。它允许我们在不阻塞主线程的情况下执行任务,从而提高应用程序的响应能力和性能。在JavaScript中,异步回调是实现异步编程的主要方式。

当我们在JavaScript中执行一个异步操作时,例如使用setTimeout()XMLHttpRequest,浏览器会创建一个回调函数,并在异步操作完成后调用该回调函数。然而,浏览器并不是在任何时候都执行回调函数。它遵循一个特定的执行顺序,这个执行顺序是由宏任务和微任务决定的。

宏任务和微任务

宏任务和微任务都是JavaScript的任务队列。宏任务队列存储了需要执行的函数,而微任务队列存储了需要执行的微任务。宏任务和微任务的区别在于它们的执行顺序。宏任务是按照FIFO(先进先出)的顺序执行的,而微任务是按照LIFO(后进先出)的顺序执行的。

宏任务的执行时机

宏任务是在页面循环的每个阶段执行的。页面循环是一个循环,它不断地执行以下步骤:

  1. 解析HTML并构建DOM树。
  2. 计算样式表并构建CSSOM树。
  3. 布局DOM树和CSSOM树,生成渲染树。
  4. 绘制渲染树。
  5. 处理用户输入事件。

在页面循环的每个阶段,浏览器都会执行宏任务队列中的所有宏任务。宏任务队列中的宏任务可以来自各种来源,例如setTimeout()setInterval()XMLHttpRequestscript标签等。

微任务的执行时机

微任务是在页面循环的每个阶段结束后执行的。也就是说,在浏览器完成页面循环的一个阶段后,它会先执行微任务队列中的所有微任务,然后再开始执行宏任务队列中的宏任务。

微任务队列中的微任务可以来自各种来源,例如Promise.then()MutationObserverDOMContentLoaded事件等。

异步回调的执行顺序

异步回调的执行顺序是由宏任务和微任务决定的。当一个异步操作完成后,浏览器会创建一个回调函数,并将该回调函数添加到宏任务队列或微任务队列中。回调函数的执行时机取决于它所在的队列。

如果回调函数位于宏任务队列中,则它将在下一个页面循环的某个阶段执行。如果回调函数位于微任务队列中,则它将在当前页面循环的末尾执行。

示例

以下示例演示了宏任务和微任务的执行顺序:

console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

输出结果为:

1
4
3
2

在该示例中,console.log('1')console.log('4')是宏任务,setTimeout()回调函数和Promise.then()回调函数是微任务。宏任务按照FIFO的顺序执行,因此console.log('1')console.log('4')首先执行。微任务按照LIFO的顺序执行,因此Promise.then()回调函数先于setTimeout()回调函数执行。因此,输出结果为1、4、3、2。

总结

宏任务和微任务是JavaScript中的两个任务队列。宏任务队列存储了需要执行的函数,而微任务队列存储了需要执行的微任务。宏任务是按照FIFO的顺序执行的,而微任务是按照LIFO的顺序执行的。异步回调的执行顺序是由宏任务和微任务决定的。