在 requestIdleCallback 中:
- 单次执行任务的时间尽量不要超过 50ms,否则可能会影响用户的交互体验
- 如果任务执行时间长,可以通过类似于 React 任务调度的方式实现时间切片来分段执行
- 浏览器非空闲时尽量不要进行 DOM 更改的操作,防止当前一帧需要重新计算页面布局
- 浏览器非空闲时尽量避免在回调中使用 Promise,因为会在回调结束后立即执行,从而可能会影响下一帧,此时可以通过 Task 执行任务,从而让出浏览器主线程的执行权
温馨提示:在
requestAnimationFrame中更改 DOM 合适吗?可以阅读 Using requestIdleCallback 获取答案。
Polifill
// RIC and shim for browsers setTimeout() without it idle
// 如果浏览器支持 requestIdleCallback,则使用 requestIdleCallback
// 否则使用 MessageChannel 或 setTimeout 来模拟 requestIdleCallback
// 注意 MessageChannel 是微任务,setTimeout 是宏任务
// 为什么 MessageChannel 比 setTimeout 更好?
// 1. MessageChannel 是微任务,setTimeout 是宏任务
// 2. MessageChannel 没有 4ms 的延迟
// 3. MessageChannel 可以在页面隐藏时继续执行,setTimeout 不能
// 都是异步任务,但是执行时机不同,MessageChannel 是微任务,setTimeout 是宏任务
// 尽管 MessageChannel 不完全等同于 requestIdleCallback,
// 因为它不能决定何时处于浏览器的空闲状态,但它提供了一种在事件循环尾部执行任务的机制
// 虽然这种方法没有直接管理浏览器空闲时间的能力,
// 但它依然可以在某些场景下作为 requestIdleCallback 的简化替代方案
let requestIdleCallback: (cb: IdleRequestCallback) => any;
if (typeof window.requestIdleCallback !== 'undefined') {
requestIdleCallback = window.requestIdleCallback;
} else if (typeof window.MessageChannel !== 'undefined') {
// The first recommendation is to use MessageChannel because
// it does not have the 4ms delay of setTimeout
const channel = new MessageChannel();
const port = channel.port2;
const tasks: IdleRequestCallback[] = [];
channel.port1.onmessage = ({ data }) => {
const task = tasks.shift();
if (!task) {
return;
}
idleCall(task, data.start);
};
requestIdleCallback = function (cb: IdleRequestCallback) {
tasks.push(cb);
port.postMessage({ start: Date.now() });
};
} else {
requestIdleCallback = (cb: IdleRequestCallback) => setTimeout(idleCall, 0, cb, Date.now());
}
windows.requestIdleCallback =requestIdleCallback