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) {// 新任务入队, 如果未过期就保存到timerQueuenewTask.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中执行// 调用出重新执行flushWorkfunction 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 就是当前的任务// 假如当前的任务没有被其他打断,正常在执行,那么二者相同// 这里返回的回调就会被重新赋值为callabckreturn 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 是以一个优先级执行一个回调函数,比如事件派发时
