跳到主要内容

宏任务与微任务

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