首先要从js的是单线程模型来讲起,Javascript执行是会经历静态编译,动态解释和事件循环作任务调度的过程,大体的流程以下(注意,该流程是以chrome浏览器内核为标准的执行流程,在node或者其余浏览器中,执行流程会有所差别,可是核心思想是差很少的。从这里面咱们很直观的认识到js的线程模型是怎么工做的。那跟scheduler有什么关系呢,咱们都知道React16采用了fiber架构,这样的架构下,使用链表结构代替原有的函数嵌套,避免没法控制组件渲染的过程的问题,Fiber让React内部会动态灵活的管理全部组件的渲染任务,能够中断暂停某一个组件的渲染,因此,对于复杂型应用来讲,对于某一个交互动做的反馈型任务,咱们是能够对其进行拆解,一步步的作交互反馈,避免在一个页面重绘时间周期内作过多的事情,这样就能减小应用的长任务,最大化提高应用操做性能,更好的利用有限的时间,那么,咱们如今能够只聚焦在任务管理上,一块儿来研究一下React究竟是如何调度组件的任务执行的,这里说渲染感受不太准确
macrotask: setTimeout, setInterval, setImmediate,MessageChannel, I/O, UI rendering netWork
microtask: process.nextTick, Promises, Object.observe(废弃), MutationObservernode
[引自大神论述]react
上图来自知乎文章chrome
通常状况下,在没有特别说明的状况下咱们会把macrotask称为task queues ,在一次的事件循环中,他只会执行一次
console.log('start'); setTimeout(function() { console.log('macrotask'); }, 0); Promise.resolve().then(function() { console.log('microtask'); }).then(function() { console.log('microtask'); }); console.log(' end');
根据上述理论本身试试程序的运行结果, 为何咱们在分析scheduler源码以前先要介绍下异步队列,由于了解清楚js异步队列才会让咱们更加清晰知道scheduler是怎么使用调度方法来更好的安排代码执行时机。浏览器
图片出自同一地方
执行JS(具体流程在上面有描述)--->计算Style--->构建布局模型(Layout)--->绘制图层样式(Paint)--->组合计算渲染呈现结果(Composite)缓存
在帧的渲染中当执行完流程和UI绘制以后 会有一部分空闲时间,若是咱们能掌握这个时间加一充分利用就更加理想
那如何知道一帧进入这个空闲时间呢,浏览器目前提供了这个回调 requestIdleCallback 即浏览器空闲时
var handle = window.requestIdleCallback(callback[, options]);
}架构
目前浏览器对于requestIdleCallback的支持不是特别完整,因此react团队放弃了requestIdleCallback的使用
本身用requestAnimationFrame和MessageChannel来polyfillapp
很简单,33毫秒,可是时间并不老是33ms,这个时间是React认为的一个能够接受的最大值,若是运行设备能作到大于30fps,那么它会去调整这个值(一般状况下能够调整到16.6ms)。调整策略是用当前每帧的总时间与实际每帧的时间进行比较,当实际时间小于当前时间且稳定(先后两次都小于当前时间),那么就会认为这个值是有效的,而后将每帧时间调整为该值(取先后两次中时间大的值),还有就是requestAnimationFrame回调的第一个参数,每一帧的起始时间,最终借助requestAnimationFrame让一批扁平的任务刚好控制在一块一块的33ms这样的时间片内执行便可异步
全部准备工做都作好了, 接下来咱们逐步来分析Scheduler源码ide
// 枚举 // 当即执行的任务 var ImmediatePriority = 1; // 用户阻塞优先级 var UserBlockingPriority = 2; // 通常的优先级 var NormalPriority = 3; // 低级的优先级 var LowPriority = 4; // 空闲的优先级 var IdlePriority = 5; // 咱们能够理解 优先级越高 过时时间就越短 反之 越长 // Max 31 bit integer. The max integer size in V8 for 32-bit systems. // Math.pow(2, 30) - 1 // 0b111111111111111111111111111111 // 最大整数 var maxSigned31BitInt = 1073741823; // Times out immediately // 超时的优先级时间 说明没有剩余时间了 须要当即被调度 var IMMEDIATE_PRIORITY_TIMEOUT = -1; // Eventually times out // 事件的过时时间 250 ms var USER_BLOCKING_PRIORITY = 250; // 通常优先级的过时时间 5000 ms var NORMAL_PRIORITY_TIMEOUT = 5000; // 优先级低的 10000ms var LOW_PRIORITY_TIMEOUT = 10000; // Never times out 空闲的任务 有没有限制了 也就是最大整数 var IDLE_PRIORITY = maxSigned31BitInt;
// Callbacks are stored as a circular, doubly linked list. // 回调保存为了循环的双向链表 var firstCallbackNode = null; // 当前是否过时 var currentDidTimeout = false; // Pausing the scheduler is useful for debugging. // 调度是否中断 var isSchedulerPaused = false; // 默认当前的优先级为通常优先级 var currentPriorityLevel = NormalPriority; // 当前时间开始时间 var currentEventStartTime = -1; // 当前过时时间 var currentExpirationTime = -1; // This is set when a callback is being executed, to prevent re-entrancy. // 当前是否执行callback 调度 var isExecutingCallback = false; // 是否有回调呗调度 var isHostCallbackScheduled = false; // 支持performance.now 函数 var hasNativePerformanceNow = typeof performance === 'object' && typeof performance.now === 'function';
function unstable_scheduleCallback(callback, deprecated_options) { //开始时间 var startTime = currentEventStartTime !== -1 ? currentEventStartTime : exports.unstable_now(); var expirationTime; // 过时时间 这里模拟的是 requestIdleCallback options的 timeout的定义 // 若是这里指定了 timeout 就会计算出 过时时间 // 若是么有指定就会根据 调度程序的优先级去计算 好比 普通是 5000 低级是 10000 空闲就永远不会过时等.... if (typeof deprecated_options === 'object' && deprecated_options !== null && typeof deprecated_options.timeout === 'number') { // FIXME: Remove this branch once we lift expiration times out of React. expirationTime = startTime + deprecated_options.timeout; } else { switch (currentPriorityLevel) { case ImmediatePriority: expirationTime = startTime + IMMEDIATE_PRIORITY_TIMEOUT; break; case UserBlockingPriority: expirationTime = startTime + USER_BLOCKING_PRIORITY; break; case IdlePriority: expirationTime = startTime + IDLE_PRIORITY; break; case LowPriority: expirationTime = startTime + LOW_PRIORITY_TIMEOUT; break; case NormalPriority: default: expirationTime = startTime + NORMAL_PRIORITY_TIMEOUT; } } // 新建一个节点 var newNode = { callback: callback, priorityLevel: currentPriorityLevel, expirationTime: expirationTime, next: null, previous: null }; // Insert the new callback into the list, ordered first by expiration, then // by insertion. So the new callback is inserted any other callback with // equal expiration. // 将新回调插入列表,首先按到期排序,而后按插入排序。因此新的回调插入到任何callback都拥有相同的过时时间 // 若是链表是空的 则 从新构建 if (firstCallbackNode === null) { // This is the first callback in the list. firstCallbackNode = newNode.next = newNode.previous = newNode; ensureHostCallbackIsScheduled(); } else { var next = null; var node = firstCallbackNode; // 先将链表根据过时时间进行排序 遍历查找 寻找比当前过时时间大的节点 do { if (node.expirationTime > expirationTime) { // The new callback expires before this one. next = node; break; } node = node.next; } while (node !== firstCallbackNode); // 没有找到比当前更靠后的 元素 说明当前的节点是最不优先的 if (next === null) { // No callback with a later expiration was found, which means the new // callback has the latest expiration in the list. // 当前新加入的节点是最后面的 指针指向链表头 next = firstCallbackNode; } else if (next === firstCallbackNode) { // The new callback has the earliest expiration in the entire list. // 说明全部的任务 firstCallbackNode = newNode; ensureHostCallbackIsScheduled(); } //将新的node 加入到链表 维护一下循环链表 var previous = next.previous; previous.next = next.previous = newNode; newNode.next = next; newNode.previous = previous; } return newNode; }
function ensureHostCallbackIsScheduled() { // 调度正在执行 返回 也就是不能打断已经在执行的 if (isExecutingCallback) { // Don't schedule work yet; wait until the next time we yield. return; } // Schedule the host callback using the earliest expiration in the list. // 让优先级最高的 进行调度 若是存在已经在调度的 直接取消 var expirationTime = firstCallbackNode.expirationTime; if (!isHostCallbackScheduled) { isHostCallbackScheduled = true; } else { // Cancel the existing host callback. // 取消正在调度的callback cancelHostCallback(); } // 发起调度 requestHostCallback(flushWork, expirationTime); }
var localSetTimeout = typeof setTimeout === 'function' ? setTimeout : undefined; var localClearTimeout = typeof clearTimeout === 'function' ? clearTimeout : undefined; // We don't expect either of these to necessarily be defined, but we will error // later if they are missing on the client. var localRequestAnimationFrame = typeof requestAnimationFrame === 'function' ? requestAnimationFrame : undefined; var localCancelAnimationFrame = typeof cancelAnimationFrame === 'function' ? cancelAnimationFrame : undefined; // requestAnimationFrame does not run when the tab is in the background. If // we're backgrounded we prefer for that work to happen so that the page // continues to load in the background. So we also schedule a 'setTimeout' as // a fallback. // TODO: Need a better heuristic for backgrounded work. var ANIMATION_FRAME_TIMEOUT = 100; var rAFID; var rAFTimeoutID; /** * 是解决网页选项卡若是在未激活状态下requestAnimationFrame不会被触发的问题, *这样的话,调度器是能够在后台继续作调度的,一方面也能提高用户体验, * 同时后台执行的时间间隔是以100ms为步长,这个是一个最佳实践,100ms是不会影响用户体验同时也不影响CPU能耗的一个折中时间间隔 为何要用 settimeout 由于requestAnimationFrame不会在tab不激活的状况下不执行 */ var requestAnimationFrameWithTimeout = function (callback) { // schedule rAF and also a setTimeout rAFID = localRequestAnimationFrame(function (timestamp) { // cancel the setTimeout localClearTimeout(rAFTimeoutID); callback(timestamp); }); rAFTimeoutID = localSetTimeout(function () { // cancel the requestAnimationFrame localCancelAnimationFrame(rAFID); callback(exports.unstable_now()); }, ANIMATION_FRAME_TIMEOUT); }; if (hasNativePerformanceNow) { var Performance = performance; exports.unstable_now = function () { return Performance.now(); }; } else { exports.unstable_now = function () { return localDate.now(); }; }
这里react 作了特别的兼容处理 注入方式和不支持window或者 MessageChannel的方式 这里不作主要分析 由于
比较简单,这里将主要研究现代浏览器的处理方式
var requestHostCallback; var cancelHostCallback; var shouldYieldToHost; var globalValue = null; if (typeof window !== 'undefined') { globalValue = window; } else if (typeof global !== 'undefined') { globalValue = global; } if (globalValue && globalValue._schedMock) { // Dynamic injection, only for testing purposes. // 动态注入 用于测试目的 var globalImpl = globalValue._schedMock; requestHostCallback = globalImpl[0]; cancelHostCallback = globalImpl[1]; shouldYieldToHost = globalImpl[2]; exports.unstable_now = globalImpl[3]; } else if ( // 非DOM环境 // If Scheduler runs in a non-DOM environment, it falls back to a naive // implementation using setTimeout. typeof window === 'undefined' || // Check if MessageChannel is supported, too. typeof MessageChannel !== 'function') { // If this accidentally gets imported in a non-browser environment, e.g. JavaScriptCore, // fallback to a naive implementation. var _callback = null; var _flushCallback = function (didTimeout) { if (_callback !== null) { try { _callback(didTimeout); } finally { _callback = null; } } }; requestHostCallback = function (cb, ms) { //这里的调度就直接在settimeout 去执行 也就是直接放入macrotask队列 这里应该是下下策 if (_callback !== null) { // Protect against re-entrancy. setTimeout(requestHostCallback, 0, cb); } else { _callback = cb; setTimeout(_flushCallback, 0, false); } }; cancelHostCallback = function () { _callback = null; }; shouldYieldToHost = function () { return false; }; } else { if (typeof console !== 'undefined') { // TODO: Remove fb.me link if (typeof localRequestAnimationFrame !== 'function') { console.error("This browser doesn't support requestAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills'); } if (typeof localCancelAnimationFrame !== 'function') { console.error("This browser doesn't support cancelAnimationFrame. " + 'Make sure that you load a ' + 'polyfill in older browsers. https://fb.me/react-polyfills'); } } // 调度的callback var scheduledHostCallback = null; // 消息发送中标识 var isMessageEventScheduled = false; // 过时时间 var timeoutTime = -1; // rAF 轮询启动状态 var isAnimationFrameScheduled = false; // 任务执行中标识 var isFlushingHostCallback = false; // 下一帧指望完成时间点,用于判断重绘后 js 线程是否空闲,仍是长期占用 var frameDeadline = 0; // We start out assuming that we run at 30fps but then the heuristic tracking // will adjust this value to a faster fps if we get more frequent animation // frames. /** * 咱们假设咱们以30fps运行,而后进行启发式跟踪 若是咱们得到更频繁的动画,我会将此值调整为更快的fps 帧 默认33 为何是33 由于咱们假定每秒30帧固定评率刷新 也就是 一帧须要33ms */ var previousFrameTime = 33; var activeFrameTime = 33; //以此推断线程是否空闲,好添加并处理新任 shouldYieldToHost = function () { return frameDeadline <= exports.unstable_now(); }; // We use the postMessage trick to defer idle work until after the repaint. // 使用postMessage 来跟踪判断重绘是否完成 var channel = new MessageChannel(); var port = channel.port2; // 当port1 发送消息后 这里在帧重绘完成后 进入message回调 接着处理咱们 // callback channel.port1.onmessage = function (event) { isMessageEventScheduled = false; var prevScheduledCallback = scheduledHostCallback; var prevTimeoutTime = timeoutTime; scheduledHostCallback = null; timeoutTime = -1; var currentTime = exports.unstable_now(); var didTimeout = false; // 说明没有时间了 当前帧给与这个callback的时间没有了 if (frameDeadline - currentTime <= 0) { // There's no time left in this idle period. Check if the callback has // a timeout and whether it's been exceeded. // 检查当前callback 是否过时 和 是否被执行 // previousFrameTime 小于等于 currentTime 时,scheduler // 认为线程不是空闲的,对于超时的任务将当即执行, // 对于未超时的任务将在下次重绘后予以处理 // 显然是超时的 而且没有被取消 直接执行 而且给与timeout 为空 if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) { // Exceeded the timeout. Invoke the callback even though there's no // time left. didTimeout = true; } else { // No timeout. //没有超时 若是没有安排轮询 就开启轮询 if (!isAnimationFrameScheduled) { // Schedule another animation callback so we retry later. isAnimationFrameScheduled = true; requestAnimationFrameWithTimeout(animationTick); } // Exit without invoking the callback. // 不执行callback 直接退出 让轮询去调度 scheduledHostCallback = prevScheduledCallback; timeoutTime = prevTimeoutTime; return; } } // 执行callback 这里应该是终点了 到这里为止 调度分析完了 // 执行完成的调度 返回的只有 是否已通过期(didTimeout) if (prevScheduledCallback !== null) { isFlushingHostCallback = true; try { prevScheduledCallback(didTimeout); } finally { isFlushingHostCallback = false; } } }; // 这里做为rAF的callback 处理函数 /** * 在 animateTick 中,scheduler 将计算下一帧指望完成时间点 previousFrameTime, 而后经过 port.postMessage 方法发送消息。等到 port1 接受到消息时,schdulear 将 previousFrameTime 与 currentTime 做比较:当 previousFrameTime 小于等于 currentTime 时, scheduler 认为线程不是空闲的,对于超时的任务将当即执行,对于未超时的任务将在下次重绘后予以处理; 当 previousFrameTime 大于 currentTime 时,线程就是空闲的,scheduler 将当即执行。这一处理机制在 port1.onMessage 监听函数中实现(做为 macrotasks,port1 接受消息的时机将随着线程的空闲程度起变化)。 */ var animationTick = function (rafTime) { //轮询了 这里进入 if (scheduledHostCallback !== null) { // Eagerly schedule the next animation callback at the beginning of the // frame. If the scheduler queue is not empty at the end of the frame, it // will continue flushing inside that callback. If the queue *is* empty, // then it will exit immediately. Posting the callback at the start of the // frame ensures it's fired within the earliest possible frame. If we // waited until the end of the frame to post the callback, we risk the // browser skipping a frame and not firing the callback until the frame // after that. /** * * 最早在帧的开头安排下一个回调。若是调度程序队列在帧的末尾不为空, * 它将继续在该回调内刷新。若是队列*为*空,则它将当即退出 *。在帧的开头触发回调可确保在最先的帧内触发。要是咱们 *等到帧结束后触发回调,咱们冒着浏览器丢帧的风险, *而且在此帧以后的不会触发回调。 */ requestAnimationFrameWithTimeout(animationTick); } else { // No pending work. Exit. isAnimationFrameScheduled = false; return; } // 调度的时间rafTime - frameDeadline 下一帧预到期 + 一帧的多少 = 给下一帧留下的时间 var nextFrameTime = rafTime - frameDeadline + activeFrameTime; // 帧的频率小于当前的 说明处理的时间都是比较短的 // 其实这里作了下调整 若是当前的设备的更新频率大于咱们设定的 30fps // 咱们就须要取更新的频率的最大值 这里的最大值的更新频率 最大值 // 咱们须要澄清一个问题 频率越大 一帧花费的时间就越短 if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) { if (nextFrameTime < 8) { // 预防性代码 也就是说 不支持刷新频率大于120hz 若是大于120 就当120处理 也就是说 一帧只有8ms // Defensive coding. We don't support higher frame rates than 120hz. // If the calculated frame time gets lower than 8, it is probably a bug. nextFrameTime = 8; } // If one frame goes long, then the next one can be short to catch up. // If two frames are short in a row, then that's an indication that we // actually have a higher frame rate than what we're currently optimizing. // We adjust our heuristic dynamically accordingly. For example, if we're // running on 120hz display or 90hz VR display. // Take the max of the two in case one of them was an anomaly due to // missed frame deadlines. //若是一帧长,那么下一帧可能很短。 // 若是两个帧连续短,那么这代表咱们实际上具备比咱们当前优化 //的帧速率更高的帧速率。咱们相应地动态调整启发式。例如,若是咱们是 // 在120hz显示屏或90hz VR显示屏上运行。 // 取两个中的最大值,以防其中一个因错过帧截止日期而异常。 activeFrameTime = nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime; } else { previousFrameTime = nextFrameTime; } // 计算下一帧过时时间 frameDeadline = rafTime + activeFrameTime; // 若是还么发送消息 就触发message 让帧重绘完成后 进行调度callback if (!isMessageEventScheduled) { isMessageEventScheduled = true; port.postMessage(undefined); } }; // requestHostCallback = function (callback, absoluteTimeout) { // 设置当前的callback scheduledHostCallback = callback; // 设置过时时间 timeoutTime = absoluteTimeout; //当前若是有任务正在执行中(意为当前没有重绘任务,重绘线程是空闲的) // 或者所添加的任务须要当即执行,scheduler 直接调用 port.postMessage 发送消息,跳过 rAF // 轮询,以使任务获得即时执行 if (isFlushingHostCallback || absoluteTimeout < 0) { // Don't wait for the next frame. Continue working ASAP, in a new event. port.postMessage(undefined); } else if (!isAnimationFrameScheduled) { // If rAF didn't already schedule one, we need to schedule a frame. // 若是raf 没有进行调度 安排一个新的 rAF轮询 // 若是rAF 没有发挥做用 在使用settimeout 去做为预备去调度 // TODO: If this rAF doesn't materialize because the browser throttles, we // might want to still have setTimeout trigger rIC as a backup to ensure // that we keep performing work. isAnimationFrameScheduled = true; //若是 rAF 轮询未启动,调用 requestAnimationFrameWithTimeout(animationTick) 启动轮询 requestAnimationFrameWithTimeout(animationTick); } }; cancelHostCallback = function () { scheduledHostCallback = null; isMessageEventScheduled = false; timeoutTime = -1; }; }
flushFirstCallback 从双向链表中取出首个任务节点并执行。若首个任务节点的 callback 返回函数,使用该函数构建新的 callbackNode 任务节点,并将该任务节点插入双向链表中:若该任务节点的优先级最高、且不仅包含一个任务节点,调用 ensureHostCallbackIsScheduled,在下一次重绘后酌情执行双向链表中的任务节点;不然只将新建立的任务节点添加到双向链表中
function flushFirstCallback() { var flushedNode = firstCallbackNode; // Remove the node from the list before calling the callback. That way the // list is in a consistent state even if the callback throws. var next = firstCallbackNode.next; if (firstCallbackNode === next) { // This is the last callback in the list. firstCallbackNode = null; next = null; } else { var lastCallbackNode = firstCallbackNode.previous; firstCallbackNode = lastCallbackNode.next = next; next.previous = lastCallbackNode; } flushedNode.next = flushedNode.previous = null; // Now it's safe to call the callback. var callback = flushedNode.callback; var expirationTime = flushedNode.expirationTime; var priorityLevel = flushedNode.priorityLevel; var previousPriorityLevel = currentPriorityLevel; var previousExpirationTime = currentExpirationTime; currentPriorityLevel = priorityLevel; currentExpirationTime = expirationTime; var continuationCallback; try { continuationCallback = callback(); } finally { // 恢复当一次的优先级 currentPriorityLevel = previousPriorityLevel; currentExpirationTime = previousExpirationTime; } // A callback may return a continuation. The continuation should be scheduled // with the same priority and expiration as the just-finished callback //. 若是callback 返回的仍是 function 须要从新调度 // 跟新加入一个节点是同样的 就不在分析了 if (typeof continuationCallback === 'function') { var continuationNode = { callback: continuationCallback, priorityLevel: priorityLevel, expirationTime: expirationTime, next: null, previous: null }; // Insert the new callback into the list, sorted by its expiration. This is // almost the same as the code in `scheduleCallback`, except the callback // is inserted into the list *before* callbacks of equal expiration instead // of after. if (firstCallbackNode === null) { // This is the first callback in the list. firstCallbackNode = continuationNode.next = continuationNode.previous = continuationNode; } else { var nextAfterContinuation = null; var node = firstCallbackNode; do { if (node.expirationTime >= expirationTime) { // This callback expires at or after the continuation. We will insert // the continuation *before* this callback. nextAfterContinuation = node; break; } node = node.next; } while (node !== firstCallbackNode); if (nextAfterContinuation === null) { // No equal or lower priority callback was found, which means the new // callback is the lowest priority callback in the list. nextAfterContinuation = firstCallbackNode; } else if (nextAfterContinuation === firstCallbackNode) { // The new callback is the highest priority callback in the list. firstCallbackNode = continuationNode; ensureHostCallbackIsScheduled(); } var previous = nextAfterContinuation.previous; previous.next = nextAfterContinuation.previous = continuationNode; continuationNode.next = nextAfterContinuation; continuationNode.previous = previous; } } }
基于 flushFirstCallback,flushImmediateWork 函数用于执行双向链表中全部优先级为 ImmediatePriority 的任务节点。若是双向链表不仅包含优先级为 ImmediatePriority 的任务节点,flushImmediateWork 将调用 ensureHostCallbackIsScheduled 等待下次重绘后执行剩余的任务节点。
function flushImmediateWork() { if ( // Confirm we've exited the outer most event handler // 确认咱们退出了最外层的事件handler // 执行全部当即执行的callback currentEventStartTime === -1 && firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority) { isExecutingCallback = true; try { do { flushFirstCallback(); } while ( // Keep flushing until there are no more immediate callbacks firstCallbackNode !== null && firstCallbackNode.priorityLevel === ImmediatePriority); } finally { isExecutingCallback = false; // 还有其余优先级的 依次轮询调度 if (firstCallbackNode !== null) { // There's still work remaining. Request another callback. ensureHostCallbackIsScheduled(); } else { isHostCallbackScheduled = false; } } } }
flushWork 做为 requestHostCallback 函数的参数,得到的首个实参 didTimeout 为是否超时的标识。若是超时,flushWork 经过调用 flushFirstCallback 批量执行全部未超时的任务节点;若果没有超时,flushWork 将在下一帧未完成前(经过 shouldYieldToHost 函数判断)尽量地执行任务节点。等上述条件逻辑执行完成后,若是双向链表非空,调用 ensureHostCallbackIsScheduled 等待下次重绘后执行剩余的任务节点。特别的,当双向链表中还存在 ImmediatePriority 优先级的任务节点,flushWork 将调用 flushImmediateWork 批量执行这些任务节点。
function flushWork(didTimeout) { // Exit right away if we're currently paused // 暂停状况下 直接退出 if (enableSchedulerDebugging && isSchedulerPaused) { return; } isExecutingCallback = true; var previousDidTimeout = currentDidTimeout; currentDidTimeout = didTimeout; try { // 若是已经超时 if (didTimeout) { // Flush all the expired callbacks without yielding. // 让firstCallbackNode 双向链表去消耗 while (firstCallbackNode !== null && !(enableSchedulerDebugging && isSchedulerPaused)) { // TODO Wrap in feature flag // Read the current time. Flush all the callbacks that expire at or // earlier than that time. Then read the current time again and repeat. // This optimizes for as few performance.now calls as possible. var currentTime = exports.unstable_now(); // 已通过期的 直接执行 if (firstCallbackNode.expirationTime <= currentTime) { do { flushFirstCallback(); } while (firstCallbackNode !== null && firstCallbackNode.expirationTime <= currentTime && !(enableSchedulerDebugging && isSchedulerPaused)); continue; } break; } } else { // Keep flushing callbacks until we run out of time in the frame. if (firstCallbackNode !== null) { do { if (enableSchedulerDebugging && isSchedulerPaused) { break; } flushFirstCallback(); // 没有超时 也不用放入下一帧的的直接执行 } while (firstCallbackNode !== null && !shouldYieldToHost()); } } } finally { isExecutingCallback = false; currentDidTimeout = previousDidTimeout; // 没有处理玩的继续的执行 if (firstCallbackNode !== null) { // There's still work remaining. Request another callback. ensureHostCallbackIsScheduled(); } else { isHostCallbackScheduled = false; } // Before exiting, flush all the immediate work that was scheduled. // 退出以前将全部当即执行的任务去执行 flushImmediateWork(); } }
由于 scheduler 使用首个任务节点的超时时间点做为 requestHostCallback 函数的次参(在 ensureHostCallbackIsScheduled 函数中处理)。所以,若是首个任务节点的优先级为 ImmediatePriority,flushWork 所得到参数 didTimeout 也将是否值,其执行逻辑将是执行全部优先级为 ImmediatePriority 的任务节点,再调用 ensureHostCallbackIsScheduled 等待下一次重绘时执行其他任务节点。若是首个任务节点的优先级为 UserBlockingPriority 等,flushWork 将执行同优先级的任务节点,再调用 ensureHostCallbackIsScheduled 等待下一次重绘时执行其他任务节点。全部对不一样优先级的任务节点,scheduler 采用分段执行的策略
function unstable_runWithPriority(priorityLevel, eventHandler) { switch (priorityLevel) { case ImmediatePriority: case UserBlockingPriority: case NormalPriority: case LowPriority: case IdlePriority: break; default: priorityLevel = NormalPriority; } var previousPriorityLevel = currentPriorityLevel; var previousEventStartTime = currentEventStartTime; currentPriorityLevel = priorityLevel; currentEventStartTime = getCurrentTime(); try { return eventHandler(); } finally { currentPriorityLevel = previousPriorityLevel; currentEventStartTime = previousEventStartTime; // Before exiting, flush all the immediate work that was scheduled. flushImmediateWork(); } }
unstable_runWithPriority(priorityLevel, eventHandler) 将 currentPriorityLevel 缓存设置为 priorityLevel,随后再执行 eventHandler,最后调用 flushImmediateWork 函数执行全部优先级为 ImmediatePriority 的任务节点,其他任务节点等待下次重绘后再执行。能够设想,当 eventHandler 为 unstable_scheduleCallback 函数时,将影响所添加任务节点的优先级,并当即执行 ImmediatePriority 优先级的任务。其实就是给执行eventHandler 设置优先级
function unstable_wrapCallback(callback) { var parentPriorityLevel = currentPriorityLevel; return function () { // This is a fork of runWithPriority, inlined for performance. var previousPriorityLevel = currentPriorityLevel; var previousEventStartTime = currentEventStartTime; currentPriorityLevel = parentPriorityLevel; currentEventStartTime = exports.unstable_now(); try { return callback.apply(this, arguments); } finally { currentPriorityLevel = previousPriorityLevel; currentEventStartTime = previousEventStartTime; flushImmediateWork(); } }; }
unstable_wrapCallback(callback) 记录当前的优先级 currentPriorityLevel,返回函数处理效果如 unstable_runWithPriority,对于 callback 中新添加的任务节点将使用所记录的 currentPriorityLevel 做为优先级。
这里能够返回的是function 将做为新的节点去插入被调度
读完scheduler源码 感受仍是挺复杂的 固然收获也是比较大的 尤为是对于浏览执行机制有了更深刻的认识 尤为调度思路让人影响时刻, 固然分析确定会有不全面或者误差的地方 欢迎大佬们指正函数