MessageChannel 与宏任务
MessageChannel 是浏览器提供的一个通信 API,用于在两个不同的执行上下文(如不同 Window、Worker 或同一个页面内的不同脚本)之间建立消息通道。但它在事件循环(Event Loop)领域还有一个经典用法:用来生成“宏任务”(MacroTask)。
宏任务是什么?
浏览器的事件循环将任务分为:
- 宏任务(MacroTask):由宿主环境(浏览器/Node)发起的任务,例如
setTimeout、setInterval、I/O 操作、UI 渲染、MessageChannel的回调等。 - 微任务(MicroTask):由 JS 引擎发起的任务,例如
Promise.then、MutationObserver、queueMicrotask。
宏任务的特点:每次事件循环只取出一个宏任务执行,执行完后会清空当前所有微任务,然后进行 UI 渲染(如果需要),再取出下一个宏任务。
MessageChannel 与宏任务的关系
MessageChannel 的 port.postMessage() 会触发另一个端口上的 onmessage 回调。这个回调被浏览器作为一个宏任务加入到任务队列中,等待事件循环调度。
相比 setTimeout(fn, 0),MessageChannel 的优势是:
- 更快的响应速度(不需要像
setTimeout那样受最小延时 4ms 的限制) - 在某些需要微任务替代品但实际要产生宏任务的场景中,用来模拟“下一个事件循环”执行。
典型用法:
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = () => {
console.log('这是一个宏任务执行的代码');
};
// 发送消息,会触发 port1 的 onmessage 宏任务
port.postMessage(null);
与其他宏任务产生方式的对比
| 方式 | 最小延迟 | 宏任务特征 | 兼容性 |
|---|---|---|---|
setTimeout(fn, 0) | ≥4ms (嵌套后) | 宏任务 | 全 |
setImmediate (Node) | 0ms | 宏任务 | 非浏览器 |
MessageChannel | 0ms(立即加入队列) | 宏任务 | IE11+, 现代浏览器 |
postMessage (跨窗口/Worker) | 0ms | 宏任务 | 全但依赖上下文 |
实际应用场景
- 实现零延迟的宏任务:例如某些需要将任务推迟到下一个宏任务周期,但又不想受
setTimeout的 4ms 限制。 - 框架/库的异步调度:Vue 2 的
nextTick实现中,曾经利用MessageChannel作为优雅降级方案(优先使用Promise(微任务),不支持时降为setTimeout或MessageChannel)。 - 避免长时间阻塞 UI:将大计算拆分为多个宏任务块,用
MessageChannel逐个调度。
注意点
MessageChannel的回调只会被触发一次(每次postMessage产生一个宏任务),如果需要重复触发,需要递归调用postMessage。- 它本质上还是宏任务,不能替代微任务(比如你无法用它来保证在 DOM 渲染前同步执行)。
简单例子:模拟 requestIdleCallback 的降级方案
const macroTask = (fn) => {
const channel = new MessageChannel();
channel.port1.onmessage = fn;
channel.port2.postMessage(null);
};
macroTask(() => console.log('下一个宏任务执行'));
总结:MessageChannel 除了用于跨上下文通信,更被开发者当作一种高精度、零延迟的宏任务产生器,在浏览器事件循环调度中占据独特位置。