宏任务与微任务
宏任务
宏任务的定义
宏任务(Macrotask)是 JavaScript 运行中被调度执行的大颗粒度任务。
页面中的大部分操作都属于宏任务,例如:
- 页面渲染事件(解析 DOM、计算布局、绘制)。
- 用户交互事件(鼠标点击、滚动、缩放等)。
- JavaScript 脚本执行。
- 网络请求完成、文件读写完成事件。
宏任务通过事件循环(Event Loop)机制进行调度。浏览器会维护多个消息队列,如普通消息队列和延迟执行队列,主线程会通过持续循环从这些队列中取出任务并执行。
宏任务的执行流程
根据 WHATWG 规范,宏任务的执行流程可简化为以下步骤:
- 从多个消息队列中选取最早的任务(
oldestTask
)。 - 记录任务的开始时间,将其设置为当前正在执行的任务。
- 执行该任务,并将其从队列中移除。
- 统计任务执行时长,继续下一任务。
这种执行方式确保了任务的顺序性与稳定性,但也因此存在一定的局限性。
宏任务的局限性
由于消息队列的任务由系统调度,JavaScript 无法细粒度地控制任务的插入顺序。因此,宏任务的执行时间间隔难以精确控制。例如:
- 在设置多个
setTimeout
回调时,系统可能在回调任务间插入渲染任务等系统级任务。 - 如果插入的任务执行时间过长,会导致后续任务的时延增加,影响整体性能。
宏任务的这种粗时间颗粒度特性,限制了其在高实时性场景中的应用。
微任务
微任务的定义
微任务(Microtask)是运行于 JavaScript 引擎中的细粒度异步任务。它的执行时机是在 当前宏任务结束后、下一个宏任务开始前。
微任务的设计初衷是为了解决宏任务时间颗粒度过大的问题。通过微任务,可以更高效地处理需要即时响应的任务。微任务的典型应用包括:
Promise
的回调。MutationObserver
监听 DOM 变化。
微任务的执行机制
每个宏任务执行时,会创建一个属于自己的 微任务队列。微任务队列的执行流程如下:
- 在当前宏任务执行完成后,检查微任务队列。
- 按照顺序执行队列中的所有微任务。
- 如果在执行微任务时产生了新的微任务,则将其立即加入队列,并继续执行,直到队列清空。
这意味着微任务的执行优先级高于后续的宏任务。
微任务的来源
微任务主要通过以下两种方式产生:
Promise
使用Promise.resolve()
或Promise.reject()
会立即生成一个微任务,并加入当前的微任务队列。MutationObserver
通过监听 DOM 节点的变化,当节点被修改时,会生成一个微任务,记录 DOM 的变化信息。
宏任务与微任务的区别
特性 | 宏任务(Macrotask) | 微任务(Microtask) |
---|---|---|
来源 | 渲染事件、网络请求、用户交互、setTimeout | Promise 、MutationObserver |
执行时机 | 主线程从消息队列中取出任务时执行 | 当前宏任务结束后、下一个宏任务开始前 |
执行优先级 | 低 | 高 |
时间精度 | 粗 | 精确 |
宏任务与微任务的关系
每一个宏任务都绑定了一个微任务队列。当一个宏任务执行完成时,JavaScript 引擎会检查并执行该任务中产生的所有微任务。只有微任务队列被清空后,才会开始执行下一个宏任务。
这种机制保证了微任务的高优先级,同时也可能带来一定的问题。例如,在一个宏任务中,如果微任务的数量过多或执行时间过长,可能会显著延长该宏任务的执行时间,从而影响页面的响应性能。
微任务的优势
高实时性
微任务的执行时机非常精确,能够在当前宏任务结束后立即执行,适合对时间精度要求高的场景。减少性能开销
微任务在当前宏任务内完成,不需要切换到新的事件循环,能够有效减少性能开销。灵活性强
微任务的执行机制更符合异步任务的需求,广泛应用于现代开发中。
总结
宏任务与微任务是 JavaScript 异步机制的重要组成部分。宏任务适合处理大颗粒度的任务调度,而微任务则通过更高优先级、更精确的执行时机补充了宏任务的不足。在实际开发中,合理利用宏任务与微任务的特性,可以编写出更加高效、现代化的代码。
了解二者的底层原理,不仅有助于你读懂复杂的代码逻辑,还能帮助你写出性能更优的程序。在未来,随着 JavaScript 生态的不断发展,微任务的应用场景和重要性仍会持续增加。