scheduler是react在浏览器负责调度整个应用独立的包,其中包含两个功能:
切片的原理就是在浏览器可以执行js的时刻去执行一些js任务, 把一个任务分成多个工作单元,在浏览器有空闲时机执行就是时间切片的意义
切片的工作通过task宏任务来做确定在空闲时机被执行,宏任务有setTimeout
还有一个新的API就是MessageChannel
执行比之更靠前
时间切片的本质是模拟的requestIdlCallback
的执行,因为这个API是在浏览器重排重绘以后如果有空余时间才会被调用,在时间循环的尾部,属于低优先级API不会阻塞页面的渲染
调度任务执行时是通过宏任务实现的。宏任务中最常见的就是setTimeout
, 但是这个task是在下一次时间环是才会执行。但是现在存在一个比这个task执行时机更靠前,那就是MessageChannel
优先级调度
scheduler的优先级和react的优先级不同,模块会对外暴漏一个方法提供使用
也就是runWithPriority:
function unstable_runWithPriority(priorityLevel, eventHandler) {
switch (priorityLevel) {
case ImmediatePriority:
case UserBlockingPriority:
case NormalPriority:
case LowPriority:
case IdlePriority:
break;
default:
priorityLevel = NormalPriority;
}
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
}
currentPriorityLevel
这个变量在执行一个回调时会被修改为当前的优先级。回调函数内部会通过获取当前的Scheduler
模块的优先级会返被返回这个变量
在scheduler中的优先级,决定了该任务的执行时机,一时间出现的多个任务会存放在两个队列里,是由小顶堆构成的优先队列,排序标准就是此任务的优先级。
优先级的本质就是过期时间。 不同的优先级以为这不同的任务过期时间
// 优先级的过期时间
// 立即执行优先级, 是过期任务立即同步执行
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// 用户与页面交互的优先级, 常见的由focus、click等,
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
// 普通优先级
var NORMAL_PRIORITY_TIMEOUT = 5000;
// 低优先级
var LOW_PRIORITY_TIMEOUT = 10000;
// 空闲优先级
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
var maxSigned31BitInt = 1073741823;
不同的优先级分出不同的任务,比如高优先级的任务是立即执行的任务, 低优先级的任务是等待执行的任务,可以分出两个队列
- timerQueue: 保存的未过期的任务,等待被执行的任务
- taskQueue: 保存已经要执行的任务, 立即被执行的任务
每次在执行时需要将taskQueue
中的任务取出并执行。也就是:
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
if (typeof options === 'object' && options !== null) {
var delay = options.delay;
if (typeof delay === 'number' && delay > 0) {
// 任务被延迟, 当前的时间在加上一个时间为任务重新设置一个超时时间
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
// 根据优先级得出任务的过期时间
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;
var newTask = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime,
expirationTime,
sortIndex: -1,
};
if (enableProfiling) {
newTask.isQueued = false;
}
if (startTime > currentTime) {
// 新任务入队, 如果未过期就保存到timerQueue
newTask.sortIndex = startTime;
push(timerQueue, newTask); // 入队
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// 因为小顶堆新加入了一个任务,顺序重新调整, 就取消上次的timeout回调, 重新开始等待超时任务
if (isHostTimeoutScheduled) {
// 取消上次的timeout回调
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
// 如果目前正在进行的任务为空,并且等待任务就是最新加入的任务
// 就加入一个timeout回调,等待过期之后放入taskQueue执行
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (enableProfiling) {
markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
// 如果此次任务为立即执行的优先级,就执行flushWork立即开始执行这个任务
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}
// 这个方法会在执行会在每次执行时都重置以下两个队列
// 在第一个加入队列未过期的任务过期时会重新把过期的任务拿到taskQueue中执行
// 调用出重新执行flushWork
function advanceTimers(currentTime) {
let timer = peek(timerQueue);
while (timer !== null) {
if (timer.callback === null) {
pop(timerQueue);
} else if (timer.startTime <= currentTime) {
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
if (enableProfiling) {
markTaskStart(timer, currentTime);
timer.isQueued = true;
}
} else {
// Remaining timers are pending.
return;
}
timer = peek(timerQueue);
}
}
// 在flushWork开始时,会取消掉等待中的任务,因为进入此方法后的任务都是立即执行的
// 函数会调用workLoop,执行完成以后会再次把等待中的任务取出
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
// 取消timeout回调,也就是等待被执行的任务
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
每次注册需要调度的任务都是一个优先级加上一个回调(回调函数内做其他事情,比如commit,交给Scheduler
包后,会根据优先级排序,先执行taskQueue
中的高优先级任务,在tmerQueue
中最快过期的任务过期时重新拿到taskQueue
中执行
在从去taskQueue中取出时有一步关键的操作:
// 当前执行的任务
const continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
// 执行以后返回值如果时要给函数,会被重新赋值给callback属性。
// 意思是这个任务没有执行完毕,需要先等等
if (typeof continuationCallback === 'function') {
currentTask.callback = continuationCallback;
markTaskYield(currentTask, currentTime);
} else {
if (enableProfiling) {
markTaskCompleted(currentTask, currentTime);
currentTask.isQueued = false;
}
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
}
如果返回值不是函数则当前任务从队列中清除
在render阶段被调度时有这样一句:
if (root.callbackNode === originalCallbackNode) {
// 任务没有执行完毕。 originalCallbackNode 就是当前的任务
// 假如当前的任务没有被其他打断,正常在执行,那么二者相同
// 这里返回的回调就会被重新赋值为callabck
return performConcurrentWorkOnRoot.bind(null, root);
}
Scheduler 包是独立于react的,这个包的优先级是scheduler包内部使用的优先级,和外部的优先级不是一个概念。包在被react使用时会有两个函数, 一个是用于将scheduler的优先级转换为react的优先级,另一个反过来。
Scheduler对外保露了一个方法unstable_runWithPriority
方法接受一个优先级和一个回调函数,执行回调时在内部获取的优先级的方法都会取到执行runWithPriority方法给到的优先级
function unstable_runWithPriority(priorityLevel, eventHandler) {
switch (priorityLevel) {
case ImmediatePriority:
case UserBlockingPriority:
case NormalPriority:
case LowPriority:
case IdlePriority:
break;
default:
priorityLevel = NormalPriority;
}
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
}
// 函数执行时用于获取当前的优先级
function unstable_getCurrentPriorityLevel() {
return currentPriorityLevel;
}
scheduleCallback
是用于以一个优先级注册一个回调函数,这个函数利用浏览器空闲时间去执行里面的任务。runWithPriority
是以一个优先级执行一个回调函数,比如事件派发时