宏任务与微任务
JavaScript 引擎在执行任务时,会根据任务的类型和来源,将其划分为宏任务(MacroTask)和微任务(MicroTask)。
宏任务
宏任务通常是由宿主环境(如浏览器)提供的异步 API 和事件产生的,包括:
script:整体代码块。 UI 渲染:页面的重绘和回流。 setTimeout:延迟执行的回调函数。 setInterval:定时执行的回调函数。 setImmediate:立即执行的回调函数(仅 IE 和 Node 支持)。 MessageChannel:通过消息通道触发的回调函数。 requestAnimationFrame:下一次页面重绘之前执行的回调函数。 用户交互事件:如点击、滚动等。 Ajax:异步网络请求完成后的回调函数。
微任务
微任务通常是由 JavaScript 语言本身提供的 API 产生的,包括:
Promise.then:Promise 的成功或失败回调。
MutationObserver:监听 DOM 变化的回调函数。
process.nextTick:Node 环境下的微任务 API。
setTimeout 与 setImmediate
setTimeout 和 setImmediate 都用于延迟执行回调函数,但它们的行为有一些细微的差异:
在浏览器环境下,setTimeout 的最小延迟时间因浏览器而异,一般为 4ms 左右。而 setImmediate 则没有最小延迟,会在当前宏任务执行完后,立即执行。
在 Node 环境下,setTimeout(fn, 0)不一定会比 setImmediate(fn)先执行。这取决于当前事件循环的状态: 如果在 I/O 回调中,setImmediate 会先执行。 如果在主模块中,setTimeout 会先执行。
示例代码
setImmediate(() => {
console.log('setImmediate');
});
setTimeout(() => {
console.log('setTimeout');
}, 0);
MessageChannel 与 postMessage
MessageChannel 提供了一种异步通信机制,允许我们创建一对关联的端口(port),然后通过 postMessage 方法在端口之间传递消息。
示例代码
const channel = new MessageChannel();
const { port1, port2 } = channel;
port1.onmessage = function (event) {
console.log('port1收到:', event.data);
port1.postMessage('port1已收到');
};
port2.onmessage = function (event) {
console.log('port2收到:', event.data);
port2.postMessage('port2已收到');
};
port1.postMessage('你好,port2');
跨模块通信
借助 MessageChannel,我们还可以实现跨模块的消息传递。
示例代码
模块 1:
const channel = new MessageChannel();
const { port1 } = channel;
const title = document.querySelector('h1');
port1.onmessage = function (event) {
title.textContent = event.data;
port1.postMessage('DOM已更新');
};
export default channel.port2;
模块 2:
import port2 from './module1.js';
port2.postMessage('Hello world');
port2.onmessage = function (event) {
console.log('收到port1的反馈:', event.data);
};
requestAnimationFrame 与 setInterval
requestAnimationFrame 和 setInterval 都常用于创建动画效果,但它们的工作方式有所不同:
setInterval 按照固定的时间间隔,重复执行回调函数。每次回调函数中的 DOM 操作,都会立即触发一次重排和重绘。
requestAnimationFrame 则会将每一帧中的所有 DOM 操作集中起来,在下一次页面重绘之前,一次性执行完这些操作,从而减少了不必要的重排和重绘。
此外,当页面处于后台或最小化时: setInterval 会继续执行,即使页面此时不可见。 requestAnimationFrame 则会自动暂停,直到页面重新可见。
因此,在制作动画时,优先使用 requestAnimationFrame 可以获得更流畅、更节能的效果。
MutationObserver 与 process.nextTick
MutationObserver 是浏览器提供的一个用于监听 DOM 变化的 API。我们可以使用它来观察某个 DOM 节点及其子树的变化,并在变化发生时执行相应的回调函数。
示例代码
const target = document.getElementById('target');
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
console.log('发生变化的节点:', mutation.target);
});
});
observer.observe(target, {
attributes: true,
childList: true,
subtree: true,
});
process.nextTick 是 Node 环境下的一个特殊的微任务 API,它可以在当前"执行栈"的尾部、下一次 Event Loop(主线程读取"任务队列")之前,触发回调函数。
process.nextTick 的优先级高于 Promise.then 和其他微任务。
示例代码
Promise.resolve().then(() => console.log('Promise'));
setTimeout(() => console.log('setTimeout'), 0);
process.nextTick(() => console.log('nextTick'));
// 输出顺序:
// nextTick
// Promise
// setTimeout