深入理解任务切片
要先理解切片得先理解浏览器一帧做些什么
浏览器一帧做些什么
- 处理事件的回调click...事件
- 处理计时器的回调
- 开始帧
- 执行requestAnimationFrame 动画的回调
- 计算机页面布局计算 合并到主线程
- 绘制
- 如果此时还有空闲时间,执行requestIdleCallback
例如要更新10000条dom数据
我们可以分成三个小任务进行更新
并且把每一段任务插入requestIdleCallback 如图
requestIdleCallback
requestIdleCallback 它提供了一种机制,允许开发者在浏览器空闲时运行低优先级的任务,而不会影响关键任务和动画的性能。
requestidlecallback 接受一个回调函数 callback 并且在回调函数中会注入参数 deadline
deadline有两个值:
deadline.timeRemaining()返回是否还有空闲时间(毫秒)deadline.didTimeout返回是否因为超时被强制执行(布尔值)
options:
{ timeout: 1000 }指定回调的最大等待时间(以毫秒为单位)。如果在指定的 timeout 时间内没有空闲时间,回调会强制执行,避免任务无限期推迟
这个案例模拟了在浏览器空闲时,渲染1000条dom元素,非常流畅
requestidlecallback 基本用法
const total = 1000; // 定义需要生成的函数数量,即1000个任务
const arr = []; // 存储任务函数的数组
// 生成1000个函数并将其添加到数组中
function generateArr() {
for (let i = 0; i < total; i++) {
// 每个函数的作用是将一个 <div> 元素插入到页面的 body 中
arr.push(function() {
document.body.innerHTML += `<div>${i + 1}</div>`; // 将当前索引 + 1 作为内容
});
}
}
generateArr(); // 调用函数生成任务数组
// 用于调度和执行任务的函数
function workLoop(deadline) {
// 检查当前空闲时间是否大于1毫秒,并且任务数组中还有任务未执行
if (deadline.timeRemaining() > 1 && arr.length > 0) {
const fn = arr.shift(); // 从任务数组中取出第一个函数
fn(); // 执行该函数,即插入对应的 <div> 元素到页面中
}
// 再次使用 requestIdleCallback 调度下一个空闲时间执行任务
requestIdleCallback(workLoop);
}
// 开始调度任务,在浏览器空闲时执行 workLoop
requestIdleCallback(workLoop,{ timeout: 1000});
为什么React不用原生requestIdleCallback实现呢?
兼容性差Safari并不支持(如下附图) caniuse.com/?search=req…控制精细度React 要根据组件优先级、更新的紧急程度等信息,更精确地安排渲染的工作执行时机requestIdleCallback(callback) 回调函数的执行间隔是 50ms(W3C规定),也就是 20FPS,1秒内执行20次,间隔较长。差异性每个浏览器实现该API的方式不同,导致执行时机有差异有的快有的慢
requestIdleCallback替代方案是什么?
MessageChannel
选择 MessageChannel 的原因,是首先异步得是个宏任务,因为宏任务中会在下次事件循环中执行,不会阻塞当前页面的更新。MessageChannel 是一个宏任务。
没选常见的 setTimeout,是因为MessageChannel 能较快执行,在 0~1ms 内触发,像 setTimeout 即便设置 timeout 为 0 还是需要 4~5ms。相同时间下,MessageChannel 能够完成更多的任务。
MDN developer.mozilla.org/zh-CN/docs/…
若浏览器不支持 MessageChannel,还是得降级为 setTimeout。
MessageChannel基本用法
MessageChanne设计初衷是为了方便 我们在不同的上下文之间进行通讯,例如web Worker,iframe 它提供了两个端口(port1 和 port2),通过这些端口,消息可以在两个独立的线程之间双向传递
// 创建 MessageChannel
const channel = new MessageChannel();
const port1 = channel.port1;
const port2 = channel.port2;
// 设置 port1 的消息处理函数
port1.onmessage = (event) => {
console.log('Received by port1:', event.data);
port1.postMessage('Reply from port1'); // 向 port2 发送回复消息
};
// 设置 port2 的消息处理函数
port2.onmessage = (event) => {
console.log('Received by port2:', event.data);
};
// 通过 port2 发送消息给 port1
port2.postMessage('Message from port2');