首先要從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源碼 感受仍是挺複雜的 固然收穫也是比較大的 尤爲是對於瀏覽執行機制有了更深刻的認識 尤爲調度思路讓人影響時刻, 固然分析確定會有不全面或者誤差的地方 歡迎大佬們指正函數