由于 requestIdleCallback
兼容性较差且不支持 Safari
,React Fiber 需要实现一个 requestIdleCallback polyfill 做浏览器兼容;
- MDN RequestIdleCallback
- MDN RequestAnimationFrame
- MDN MessageChannel
以下为其使用 MessageChannel + requestAnimationFrame
实现 requestIdleCallback
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<script>
// 休眠函数
const sleep = (delay) => {
let startTime = Date.now();
while (delay + startTime > Date.now()) {}
};
// 任务列表
let works = [
() => {
console.log("任务 1 开始");
sleep(20);
console.log("任务 1 结束");
},
() => {
console.log("任务 2 开始");
sleep(20);
console.log("任务 2 结束");
},
() => {
console.log("任务 3 开始");
sleep(20);
console.log("任务 3 结束");
},
() => {
console.log("任务 4 开始");
sleep(20);
console.log("任务 4 结束");
},
() => {
console.log("任务 5 开始");
sleep(20);
console.log("任务 5 结束");
},
];
const { port1, port2 } = new MessageChannel();
const activeTimeFrame = 1000 / 60; // 帧率
let deadFrameTime; // 每一帧的结束时间
let pendingCallback; // requestIdleCallback的回调
let timeRemaining = () => deadFrameTime - performance.now(); // 剩余时间
window.requestIdleCallback = function (callback, option) {
window.requestAnimationFrame(function (rafTime) {
// resTime 表示 每一帧开始时间
pendingCallback = callback;
// 每一帧的结束时间(开始时间 + 执行时间 = 结束时间)
deadFrameTime = rafTime + activeTimeFrame;
port1.postMessage("port1的通知");
});
};
port2.onmessage = function (e) {
console.log("port2:", e.data);
// performance.now() 页面导航到现在的时间
// 当前时间 大于等于 当前帧结束时间,则当前帧已过期
// 当前帧是否已过期
let didTimeOut = performance.now() >= deadFrameTime;
if (didTimeOut || timeRemaining() > 0) {
if (pendingCallback) {
pendingCallback({ didTimeOut, timeRemaining });
}
}
};
const workLoop = (idleDeadline) => {
console.log('本帧剩余时间:', idleDeadline.timeRemaining());
while (idleDeadline.timeRemaining() > 0 && works.length > 0) {
// 取出待执行任务进行执行
const work = works.shift();
work();
}
if (works.length > 0) {
console.log(`只剩余${idleDeadline.timeRemaining()},本帧时间已经到期了,等待下次调度`);
requestIdleCallback(workLoop);
}
};
// timeout:浏览器空闲时执行,但是如果已经过期了,不管还有没有空闲时间,否要执行workLoop
requestIdleCallback(
workLoop
// { timeout: 1000 }
);
</script>
</body>
</html>