微任务。微任务可以在实时性和效率之间做一个有效的权衡。
前面我们已经介绍过了,页面中的大部分任务都是在主线程上执行的,这些任务包括了:
- 渲染事件(如解析 DOM、计算布局、绘制);
- 用户交互事件(如鼠标点击、滚动页面、放大缩小等);
- JavaScript 脚本执行事件,setTimeout,setInterval,postMessage、MessageChannel,setImmediate;
- 网络请求完成、文件读写完成事件。
为了协调这些任务有条不紊地在主线程上执行,页面进程引入了消息队列和事件循环机制
渲染进程内部会维护多个消息队列,比如 延迟执行队列
和 普通的消息队列
。
主线程采用一个 for 循环,不断地从这些任务队列中取出任务并执行任务。我们把这些消息队列中的任务称为宏任务。
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合了,比如监听 DOM 变化的需求。
微任务就是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。
微任务主要包括:
Promise.then
或Promise.catch:Promise
的解决或拒绝时的回调函数。process.nextTick
:这是Node.js
特有的一个函数,事件循环的每个阶段完成后调用。MutationObserver
:当DOM
发生变化时的回调函数。queueMicrotask
:这个API
能让你直接在微任务队列中添加任务。
在当前宏任务中的 JavaScript 快执行完成时,JavaScript 引擎会检查全局执行上下文中的微任务队列,然后按照顺序执行队列中的微任务。
如果在执行微任务的过程中,产生了新的微任务,同样会将该微任务添加到当前的微任务队列中。也就是说在执行微任务过程中产生的新的微任务并不会推迟到下个宏任务中执行,而是在当前的宏任务中继续执行。
结论:
- 微任务和宏任务是绑定的,每个宏任务在执行时,会创建自己的微任务队列。
- 微任务的执行时长会影响到当前宏任务的时长。
- 在一个宏任务中,分别创建一个用于回调的宏任务和微任务,无论什么情况下,微任务都早于宏任务执行。
setTimeout(function () {
console.log("1");
}, 0);
Promise.resolve().then(function () {
console.log("2");
});
在一个宏任务 script 代码
中,分别创建一个用于回调的宏任务 ( setTimeout
) 和微任务( Promise.resolve().then
),无论什么情况下,微任务都早于宏任务执行。打印结果为 2
, 1
。