返回

JS 中的微任务与宏任务:揭开它们的神秘面纱

前端

面试官们喜爱出一些棘手的问题来考察候选人的前端基础知识,而关于 JS 中微任务和宏任务的问题就是其中之一。深入了解这些概念不仅对于理解 JS 运行时至关重要,而且对于解决棘手的编码谜题和优化代码性能也大有裨益。

本文将带你踏上一段探险之旅,深入了解 JS 中微任务和宏任务的微妙世界。我们将探讨这些概念的基本原理,它们之间的相互作用,以及它们对我们编写健壮且高效代码的影响。

微任务与宏任务:一个直观的类比

想象一家繁忙的餐厅,其中主线程就像一位忙碌的厨师,负责准备食物。微任务和宏任务就像等待就餐的顾客。

  • 微任务: 就像那些耐心等待着食物的VIP顾客。他们会优先处理,在厨师完成主餐之前就得到服务。
  • 宏任务: 就像那些愿意等待的普通顾客。他们会在微任务都得到处理后才轮到就餐。

微任务的起源

微任务是 JavaScript 中的一种特殊任务类型,它们起源于以下异步操作:

  • Promise 的解析
  • MutationObserver 回调
  • requestAnimationFrame 回调

当这些操作完成时,它们会将相应的微任务添加到事件队列中。

宏任务的起源

宏任务是另一种 JavaScript 任务类型,它们起源于以下操作:

  • setTimeout 和 setInterval 回调
  • DOM 事件处理程序(例如 click、scroll)
  • XMLHttpRequest 请求

与微任务不同,宏任务会在主线程中排队,只有在前一个宏任务完成后才会执行。

微任务和宏任务的执行顺序

JS 事件队列遵循以下执行顺序:

  1. 微任务: 所有微任务都会在宏任务之前执行。
  2. 宏任务: 所有宏任务都会按照先入先出的顺序执行。

这意味着,即使宏任务先进入队列,它也必须等待所有微任务执行完毕后才能运行。

实际应用:一个面试题示例

头条的一道面试题中给出了以下代码片段:

setTimeout(() => {
  console.log('宏任务 1');
}, 0);

Promise.resolve().then(() => {
  console.log('微任务 1');
});

console.log('主线程');

Promise.resolve().then(() => {
  console.log('微任务 2');
});

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

按照微任务和宏任务的执行顺序,该代码的输出为:

主线程
微任务 1
宏任务 1
微任务 2
宏任务 2

优化技巧

理解微任务和宏任务的概念对于优化代码性能至关重要。以下是一些技巧:

  • 优先使用微任务: 由于微任务比宏任务优先执行,因此将耗时的操作放入 Promise 或 MutationObserver 中可以提高响应性。
  • 谨慎使用宏任务: 虽然宏任务对于处理不紧急的任务很有用,但过度使用它们会阻塞主线程,从而导致性能下降。
  • 利用队列微任务: 通过将微任务队列化,你可以防止大量微任务同时执行,从而避免过度消耗资源。

总结

掌握 JS 中微任务和宏任务的概念是编写健壮且高效代码的关键。通过优先使用微任务,谨慎使用宏任务,并利用队列微任务,你可以优化代码性能,解决棘手的编码谜题,并在面试中展现你的前端功底。