前言:
先看一下flushWork
在 React源碼解析之requestHostCallback 中哪裏用到了:javascript
xxx
requestHostCallback(flushWork)
xxx
xxx
requestHostCallback = function(callback) {
scheduledHostCallback = callback;
}
...
...
channel.port1.onmessage = function(event) {
xxx
xxx
const hasMoreWork = scheduledHostCallback(
hasTimeRemaining,
currentTime,
);
xxx
xxx
}
複製代碼
flushWork
=>callback
=>scheduledHostCallback
=>hasMoreWork
對,就是這個hasMoreWork
調用了flushWork
,本文就講解下flushWork()
方法。php
1、flushWork
做用:
刷新調度隊列,執行調度任務java
源碼:react
//const hasTimeRemaining = frameDeadline - currentTime > 0
//hasTimeRemaining 是每一幀內留給 react 的時間
//initialTime 即 currentTime
function flushWork(hasTimeRemaining, initialTime) {
// Exit right away if we're currently paused
//若是 React 沒有掌握瀏覽器的控制權,則不執行調度任務
if (enableSchedulerDebugging && isSchedulerPaused) {
return;
}
// We'll need a host callback the next time work is scheduled.
//調度任務執行的標識
//調度任務是否執行
isHostCallbackScheduled = false;
//調度任務是否超時
//一旦超時,
if (isHostTimeoutScheduled) {
// We scheduled a timeout but it's no longer needed. Cancel it.
isHostTimeoutScheduled = false;
/*cancelHostCallback*/
cancelHostTimeout();
}
let currentTime = initialTime;
// 檢查是否有不過時的任務,並把它們加入到新的調度隊列中
advanceTimers(currentTime);
/*isExecutingCallback 是否正在調用callback*/
isPerformingWork = true;
try {
// 若是在一幀內執行時間超時,沒有時間讓 React 執行調度任務的話
if (!hasTimeRemaining) {
// Flush all the expired callbacks without yielding.
// TODO: Split flushWork into two separate functions instead of using
// a boolean argument?
//一直執行過時的任務,直到到達一個不過時的任務爲止
while (
/*firstTask即firstCallbackNode*/
firstTask !== null &&
//若是firstTask.expirationTime一直小於等於currentTime的話,則一直執行flushTask方法
firstTask.expirationTime <= currentTime &&
!(enableSchedulerDebugging && isSchedulerPaused)
) {
/*flushTask即flushFirstCallback*/
flushTask(firstTask, currentTime);
currentTime = getCurrentTime();
//檢查是否有不過時的任務,並把它們加入到新的調度隊列中
advanceTimers(currentTime);
}
} else {
// Keep flushing callbacks until we run out of time in the frame.
//除非在一幀內執行時間超時,不然一直刷新 callback 隊列
//仍有時間剩餘而且舊調度隊列不爲空時,將不過時的任務加入到新的調度隊列中
if (firstTask !== null) {
do {
flushTask(firstTask, currentTime);
currentTime = getCurrentTime();
advanceTimers(currentTime);
} while (
firstTask !== null &&
!shouldYieldToHost() &&
!(enableSchedulerDebugging && isSchedulerPaused)
);
}
}
// Return whether there's additional work
if (firstTask !== null) {
return true;
} else {
if (firstDelayedTask !== null) {
// 執行延期的任務
requestHostTimeout(
handleTimeout,
firstDelayedTask.startTime - currentTime,
);
}
return false;
}
} finally {
isPerformingWork = false;
}
}
複製代碼
解析:
(1) 判斷 React 是否掌握瀏覽器的控制權,若是沒有,則不執行調度任務git
(2) 若是調度任務超時,則調用cancelHostTimeout()
,取消執行調度任務github
(3) 調用advanceTimers()
,檢查是否有不過時的任務,並把它們加入到新的調度隊列中web
(4) 若是能執行到此步,意味着能夠執行調度任務,設isPerformingWork
爲true瀏覽器
(5) 若是 React 的執行時間沒有剩餘,可是調度隊列存在,且調度任務過時時
① 調用flushTask()
,將調度任務從調度隊列中拿出並執行,以後將調度任務生出的子調度任務插入到其後
② 調用getCurrentTime()
,刷新當前時間
③ 調用advanceTimers()
,檢查是否有不過時的任務,並把它們加入到新的調度隊列中安全
(6) 若是 React 的執行時間有剩餘,可是調度隊列存在,且調度任務未被中斷時
① 調用flushTask()
,將調度任務從調度隊列中拿出並執行,執行調度任務生出的子調度任務
② 調用getCurrentTime()
,刷新當前時間
③ 調用advanceTimers()
,檢查是否有不過時的任務,並把它們加入到新的調度隊列中app
(7) 若是調度任務都執行完畢,則返回 true,不然返回 false,執行延期的任務
2、cancelHostTimeout
做用:
取消執行調度任務
源碼:
cancelHostTimeout = function() {
localClearTimeout(timeoutID);
timeoutID = -1;
};
複製代碼
解析:
在 React源碼解析之requestHostCallback 中有涉及到,就是取消 B 執行調度任務
3、advanceTimers
做用:
檢查是否有不過時的任務,並把它們加入到新的調度隊列中
源碼:
//檢查是否有不過時的任務,並把它們加入到新的調度隊列中
function advanceTimers(currentTime) {
// Check for tasks that are no longer delayed and add them to the queue.
//開始時間已經晚於當前時間了
if (firstDelayedTask !== null && firstDelayedTask.startTime <= currentTime) {
do {
const task = firstDelayedTask;
const next = task.next;
//調度任務隊列是一個環狀的鏈表
//說明只有一個過時任務,將其置爲 null
if (task === next) {
firstDelayedTask = null;
}
//將當前的 task 擠掉
else {
firstDelayedTask = next;
const previous = task.previous;
previous.next = next;
next.previous = previous;
}
//讓 task 擺脫與舊的調度隊列的依賴
task.next = task.previous = null;
//將 task 插入到新的調度隊列中
insertScheduledTask(task, task.expirationTime);
} while (
firstDelayedTask !== null &&
firstDelayedTask.startTime <= currentTime
);
}
}
複製代碼
解析:
(1) firstDelayedTask 表示過時的任務
(2) 若是 過時任務存在,而且仍未執行,則將該 task 拿出
(3) 調用insertScheduledTask
將 task 插入到新的調度隊列中
4、insertScheduledTask
做用:
將 newTask 插入到新的調度隊列中
源碼:
//將 newTask 插入到新的調度隊列中
function insertScheduledTask(newTask, expirationTime) {
// Insert the new task into the list, ordered first by its timeout, then by
// insertion. So the new task is inserted after any other task the
// same timeout
if (firstTask === null) {
// This is the first task in the list.
firstTask = newTask.next = newTask.previous = newTask;
} else {
var next = null;
var task = firstTask;
//React對傳進來的 callback 進行排序,
// 優先級高的排在前面,優先級低的排在後面
do {
if (expirationTime < task.expirationTime) {
// The new task times out before this one.
next = task;
break;
}
task = task.next;
} while (task !== firstTask);
//優先級最小的話
if (next === null) {
// No task with a later timeout was found, which means the new task has
// the latest timeout in the list.
next = firstTask;
}
//優先級最高的話
else if (next === firstTask) {
// The new task has the earliest expiration in the entire list.
firstTask = newTask;
}
//插入 newTask
var previous = next.previous;
previous.next = next.previous = newTask;
newTask.next = next;
newTask.previous = previous;
}
}
複製代碼
解析:
這段比較簡單,主要是鏈表的一些操做,邏輯就是:
按照傳進來的 task 的優先級高低排序,並插入到新的調度隊列中
5、flushTask
做用:
將調度任務從調度隊列中拿出,並執行,以後將調度任務生出的子調度任務插入到其後
源碼:
// 將調度任務從調度隊列中拿出,並執行;
// 將調度任務生出的子調度任務插入到其後
function flushTask(task, currentTime) {
// Remove the task from the list before calling the callback. That way the
// list is in a consistent state even if the callback throws.
// 將過時的任務在調度前從調度隊列中移除,以讓調度隊列的任務均保持不過時(一致)的狀態
const next = task.next;
// 若是當前隊列中只有一個回調任務,則清空隊列
if (next === task) {
// This is the only scheduled task. Clear the list.
firstTask = null;
}
else {
// Remove the task from its position in the list.
//若是當前任務正好等於firstTask,則firstTask指向下一個回調任務
if (task === firstTask) {
firstTask = next;
}
// 將該 task 從調度隊列中拿出來
const previous = task.previous;
previous.next = next;
next.previous = previous;
}
// 單獨拿出 task,以便安全地執行它
task.next = task.previous = null;
// Now it's safe to execute the task.
var callback = task.callback;
// 以前的調度優先級
var previousPriorityLevel = currentPriorityLevel;
// 以前的調度任務
var previousTask = currentTask;
// 當前任務
currentPriorityLevel = task.priorityLevel;
currentTask = task;
// 回調任務返回的內容
var continuationCallback;
try {
// 當前的回調任務是否超時,false 超時,true 沒有
var didUserCallbackTimeout = task.expirationTime <= currentTime;
// 執行回調任務,返回的結果由 continuationCallback 保存
continuationCallback = callback(didUserCallbackTimeout);
} catch (error) {
throw error;
} finally {
// 重置任務優先級和任務
currentPriorityLevel = previousPriorityLevel;
currentTask = previousTask;
}
// A callback may return a continuation. The continuation should be scheduled
// with the same priority and expiration as the just-finished callback.
// 調度任務可能會有返回的內容,若是返回的是一個 function,
// 該 function 應該和剛剛執行的 callback 同樣,有一樣的優先級
if (typeof continuationCallback === 'function') {
var expirationTime = task.expirationTime;
// 將回調任務的結果再拼成一個子回調任務
var continuationTask = {
callback: continuationCallback,
priorityLevel: task.priorityLevel,
startTime: task.startTime,
expirationTime,
next: null,
previous: null,
};
// Insert the new callback into the list, sorted by its timeout. This is
// almost the same as the code in `scheduleCallback`, except the callback
// is inserted into the list *before* callbacks of equal timeout instead
// of after.
// 若是調度隊列爲空的話,將子回調任務插入調度隊列
if (firstTask === null) {
// This is the first callback in the list.
firstTask = continuationTask.next = continuationTask.previous = continuationTask;
}
//判斷子回調任務的優先級
else {
var nextAfterContinuation = null;
var t = firstTask;
// 若是當前調度優先級小於 firstTask 的優先級的話,
// 下一個要執行的調度任務就是 firstTask
// ps:可是這個循環感受不會執行,由於 var t = firstTask;
do {
if (expirationTime <= t.expirationTime) {
// This task times out at or after the continuation. We will insert
// the continuation *before* this task.
nextAfterContinuation = t;
break;
}
t = t.next;
} while (t !== firstTask);
if (nextAfterContinuation === null) {
// No equal or lower priority task was found, which means the new task
// is the lowest priority task in the list.
//沒有相同或更低的優先級的調度任務找到,意味着新任務就是最低優先級的任務
nextAfterContinuation = firstTask;
}
//不然新任務是最高優先級的任務
else if (nextAfterContinuation === firstTask) {
// The new task is the highest priority task in the list.
firstTask = continuationTask;
}
// 將子回調任務插入調度隊列中
const previous = nextAfterContinuation.previous;
previous.next = nextAfterContinuation.previous = continuationTask;
continuationTask.next = nextAfterContinuation;
continuationTask.previous = previous;
}
}
}
複製代碼
解析:
(1) 將 task 從調度隊列中拿出
(2) 執行該 task,返回的結果由continuationCallback
保存
(3) 若是continuationCallback
返回的是一個function
,將該回調任務的結果再拼成一個子回調任務
(4) 將子回調任務插入調度隊列中
6、總結
本文的源碼邏輯不算複雜,可是須要熟悉鏈表的操做。
GitHub:
github.com/AttackXiaoJ…
(完)