MessageChannel是什么?
- MessageChannel可以帮助我们创建一个消息通道,并通过它的两个
MessagePort
属性发送数据。
- react使用MessageChannel来模拟requestIdleCallback将回调延迟至绘制操作之后执行。
- MessageChannel创建一个通信的管道,这个管道有两个端口,每个端口都可以通过postMessage发送数据,其中一个端口只要绑定了onmessage回调方法,就可以接收从另一个端口传过来的数据
- MessageChannel是一个宏任务。
为什么不直接使用requestIdleCallback?
- 什么是requestIdleCallback及其作用?
- requestIdleCallback是浏览器原生提供的一个api,使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。
- 正常帧任务完成后没超过 16 ms,说明时间有富余,此时就会执行
requestIdleCallback
里注册的任务
- FPS(frame per second) 是指网页每秒传输的帧数,可以理解为刷新率,每秒刷新的频率越高,画面越流畅,一般来说显示器的刷新频率为60Hz,即每秒刷新60次,每秒单帧所需时间为1000ms/60 =16.6ms
- 每一帧都做了哪些事情呢?
- 输入事件(连续事件touch,离散事件click)
- javascript代码(定时器)
- 开始帧(window resize、scroll)
- RequestAnimationFrame(callbacks 如果需要渲染)
- 布局(计算样式,更新布局)
- 绘制(分层,绘制)
- 空闲时间(执行idle callback也就是requestIdleCallback的回调)

- requestIdleCallback存在什么问题?
- 浏览器兼容问题(目前不支持Safari浏览器)
- 稳定性差(正常情况下渲染一帧时长控制在16.67ms,requestIdleCallback FPS只有20ms )
- requestIdleCallback 目的主要是用来处理不重要且不紧急的任务,因为React渲染内容并非是不重要且不紧急;
MessageChannel简单使用
const { port1, port2 } = new MessageChannel();
port1.onmessage = function(event) {
console.log("port1收到的数据:", event.data);
}
port2.onmessage = function(event) {
console.log("port2收到的数据:", event.data);
}
port1.postMessage("port1发送的数据");
port2.postMessage("port2发送的数据");
react Scheduler过程
- 当我们调用setState函数时,会触发组件更新,react会请求当前fiber最新的车道lane再结合当前的更新信息(如action,next指针指向下一个更新)生成一个update对象,然后将update对象入队缓存,开启调度更新。
- 首先将当前更新的lane合并到root,然后从根节点自上而下开启调度。
- 向 Scheduler 中存入一个任务,react在调和阶段更新一个fiber后,会暂停,然后询问Scheduler是否需要暂停,如果不需要暂停(还有时间片)则继续更新下一个fiber。
- 如果需要暂停,则react会返回一个函数,该函数用于告诉 Scheduler 任务还没有完成,并且react会使用MessageChannel开启一个宏任务,以便在一帧继续更新还没有完成的任务。
react Scheduler模块的主要作用
- 可以控制javacscript代码的暂停,将执行权归还给浏览器,让浏览器有机会更新页面。
- 在未来的某个时刻继续调度任务,执行上次还未完成的任务。
- 假如Scheduler在调度完成一个fiber工作循环后,发现没有时间片了,这个时候它要暂停掉js代码的执行,让浏览器有时间刷新页面,那么暂停之后如何才能接上呢,这个时候是不是需要新开启一个宏任务,目地是让浏览器完成本帧的渲染,在下个工作循环中继续调度剩下未完成的任务。
- 这里的时间片react默认是每帧5ms,也就是一帧当中正常情况下留下来5ms时间执行调度任务(这个时间随着设备fps的不同也会改变)
为什么不使用setTimeout
- MessageChannel的执行时机会早于setTimeout
- setTimeout(fn,0) 所创建的宏任务,会有至少 4ms 的执行时差
- 如果当前环境不支持 MessageChannel 时,会默认使用 setTimeout
为什么不使用# requestAnimationFrame
- requestAnimationFrame执行时机是页面渲染前,将React Task放到RAF中,依然有可能会阻塞渲染
- 有可能过了几次loop才调用一次RAF,React Task就会被搁置太久(浏览器并没有规定应该何时渲染页面,因此RAF是不稳定的)
MessageChannel还能用来做什么?
- 深拷贝
- MessageChannel的消息在发送和接收的过程需要序列化和反序列化。利用这个特性,我们可以实现
深拷贝
function deepClone(obj) {
return new Promise((resolve, reject) => {
try {
const { port1, port2 } = new MessageChannel();
port2.onmessage = function (e) {
resolve(e.data);
};
port1.postMessage(obj);
} catch (e) {
reject(e);
}
});
}
const oldObj = { a: { b: 1 } };
deepClone(oldObj).then((newObj) => {
console.log(oldObj === newObj);
newObj.a.b = 2;
console.log(oldObj.a.b);
});
- MessageChannel实现两个worker的直接通信
- window与单个
iframe
或者多个iframe
之间的通信可以使用MessageChannel
- MessageChannel可以作为简单的EventEmitter做事件的订阅发布,实现不同脚本之间的通信
export default function a(port) {
port.postMessage({ from: 'a', message: 'ping' });
}
export default function b(port) {
port.onmessage = (e) => {
console.log(e.data);
};
}
import a from './a.js';
import b from './b.js';
const { port1, port2 } = new MessageChannel();
b(port2);
a(port1);