workLoop是 实现时间切片 和 可中断渲染的核心,简要说明如下:
// 并发任务的入口
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
// 有任务 & 是否需要中断
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
const scheduler = {
// 任务放到队列里,等待空闲执行
taskQueue: [
{
// 每个任务是个回调的概念, 且回调任务是可中断的
callback: workLoopConcurrent
}
],
// 判断: 是否需要中断, 将控制权交给主进程
shouldYieldToHost() {
// 没有剩余时间
if (currentTime >= deadline) {
// 但需要渲染 和 有更高优任务
if (needsPaint || scheduling.isInputPending()) {
return true; // 中断
}
// 是否超过 300ms
return currentTime >= maxYieldInterval;
}
// 还有剩余时间
return false;
},
// 执行入口可见
workLoop() {
// 当前第一个任务
currentTask = taskQueue[0];
// 每次 currentTask 退出 就是一个时间切切片
while (currentTask !== null) {
// 任务没有过期, 但一帧已经无可用时间 或 需要被中断, 则让出主线程
// 每一次执行均进行超时检测,做到让出主线程。
// expirationTime >currentTime: 任务已过期
// hasTimeRemaining :有剩余时间
// shouldYieldToHost:是否暂停任务,让出主线程
if (currentTask.expirationTime > currentTime
&& (!hasTimeRemaining || shouldYieldToHost())) {
break;
}
// 表示任务到了过期时间,并且有剩余时间来执行,没有到达需要浏览器渲染的时候
// 那么我们执行任务
const callback = currentTask.callback;// 拿到任务
const continuationCallback = callback(didUserCallbackTimeout);// 执行任务
// 如果该任务后, 还有连续回调
if (typeof continuationCallback === 'function') {
// 则保留当前
currentTask.callback = continuationCallback;
} else {
// 将currentTask移除该队列
pop(taskQueue);
}
// 更新currentTask,取出任务优先级最高的那个任务
currentTask = peek(taskQueue);
}
},
}
简而言之:
有个任务队列 queue,该队列存放可中断的任务。
workLoop对队列里取第一个任务currentTask,进入循环开始执行。
如果任务执行完后,还有连续的回调,则 currentTask.callback = continuationCallback
否则移除已完成的任务
当该任务没有时间 或 需要中断 (渲染任务 或 其他高优任务插入等),则让出主线程。
否则执行任务 currentTask.callback()
更新任务currentTask,继续循环走起。
这里还涉及更多细节,例如:
requestAnimationFrame 计算一帧的空余时间;
使用new MessageChannel () 执行宏任务;
优先级;
…
这里不做详细说明。