React Fiber的时间分片调度逻辑
一、大概代码
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1; // timeRemaining是一个实时计算当前桢剩余时间的函数
}
if (nextUnitOfWork) {
requestIdleCallback(workLoop); // 还有任务就下一帧继续
} else {
commitRoot(); // 所有任务完成,提交更新
}
}
requestIdleCallback(workLoop);
// requestIdleCallback的回调函数入参deadline有2个重要属性:
timeRemaining()
:返回当前帧剩余的毫秒数,React 会用它判断还能不能继续执行任务; didTimeout
:表示任务是否已经超时(有超时时间时设置)
二、宏任务和微任务
类型 | 举例 | 执行时机 |
---|---|---|
宏任务(macrotask) | setTimeout , setInterval , setImmediate , MessageChannel , UI 事件 |
一轮事件循环的起点 |
微任务(microtask) | Promise.then , MutationObserver , queueMicrotask |
当前宏任务执行完之后立即 |
-
宏任务驱动事件循环,每一轮宏任务是整个事件循环的主角
-
微任务只是挂在当前宏任务后执行(所以同一循环的宏任务和微任务,微任务先执行,因为微任务在本次事件循环结束前执行;宏任务是下一次事件循环中执行)
“帧刷新”与“事件循环”不完全同步
有可能一次帧里跑多个事件循环(宏任务 + 微任务),也可能一次事件循环跨多帧(如果任务执行太久)三、仿照实现
let deadline = 0; const frameInterval = 5; // 时间片:5ms // 获取当前时间(更精准) function getCurrentTime() { return performance.now(); } function shouldYield() { return getCurrentTime() >= deadline; } // 模拟大任务:遍历很多数字 function runBigTask(callback) { let i = 0; const total = 10000; function workLoop() { deadline = getCurrentTime() + frameInterval; while (i < total && !shouldYield()) { callback(i); // 执行一个任务单元 i++; } if (i < total) { schedulePerformWork(); // 时间片不够了,下一帧继续 } else { console.log("任务完成"); } } schedulePerformWork(workLoop); } // 使用 MessageChannel 实现精确调度 let scheduledCallback = null; const channel = new MessageChannel(); channel.port1.onmessage = () => { if (scheduledCallback) scheduledCallback(); }; function schedulePerformWork(callback) { scheduledCallback = callback || scheduledCallback; channel.port2.postMessage(null); } // 点击开始执行 startBtn.addEventListener("click", () => { log.textContent = ""; runBigTask((i) => { if (i % 100 === 0) { log.textContent += `处理第 ${i} 项\n`; } }); });