共计 4953 个字符,预计需要花费 13 分钟才能阅读完成。
hello,这里是潇晨,明天来讲个故事
讲个故事:
从前,有家 z 公司,z 公司的 ceo 叫react
,它收下有个小弟或者叫小 leader,schedule
schedule
每天负责消化老大 react
画的大饼,而后将拆解成一个个小小的task
,给上面的小弟去实现,并且负责划分优先级,调度小弟们的工作排序。
那 schedule
怎么给这些工作划分优先级呢,它想了个最简略的方法,用 deadline
或者过期工夫,给这些 task
划分优先级,过期工夫越短阐明这个工作越紧急,连忙调配苦力(上面的小弟)去实现,过期工夫越长,阐明这个 task
越不紧急,能够当前缓缓干,还有一类 task
曾经过了它的deadline
,这个过期的工作优先级最高,没方法,延期之后也是要实现的,可怜了程序员小哥哥了。
于是小 leader,scheduler
把老板的饼掰碎了,而后给这些小 task
依照 deadline
排了个优先级,于是程序员小哥哥开始接工作了
程序员小哥哥 A 承受了 task1
和task2
,于是他给本人排了个工作清单,依照优先级先做 task1
,而后做task2
,于是小哥进入密集开发中(render
阶段),正在做task1
然而天又不测风云,老板依据业务的须要,给 scheduler
下达了一个十分紧急的需要,苦了程序员小哥了,scheduler
说,唉,没方法呀,加加班,把这个十分紧急的需要现插个队吧,程序员小哥单线程的,每次只能做一个 task
,于是插个队,加加班做最紧急的需要task0
吧。
接下来就是密集的加班中。。。(这一阶段称为 render
阶段)
终于在不屑的致力下,最终程序员小哥还是加班加点的把所有工作实现了,交给测试验证(commit
阶段),
以上状况是有紧急任务时候的打断,还有一种状况是老板给的大饼很难消化,然而这个 task2
还没达到 deadline
,程序员小哥在做这个工作的时候遇到了艰难,所以就先放一放吧,反正是个艰巨的工作,在闲暇的时候在做吧,先实现优先级高的task0
和task1
,有工夫在做task2
进入正题:
当咱们在相似上面的搜寻框组件进行搜寻时会发现,组件分为搜寻局部和搜寻后果展现列表,咱们冀望输入框能立即响应,搜素列表能够有期待的工夫,如果搜寻列表数据量很大,在进行渲染的时候,咱们又输出了一些文字,因为用户输出事件的优先级是很高的,所以就要进行后果列表的渲染,这就引出了不同工作之间的优先级和调度
Scheduler
咱们晓得如果利用占用较长的 js
执行工夫,比方超过了设施一帧的工夫,那么设施的绘制就会呈现不晦涩的景象。
Scheduler
次要的性能是工夫切片和调度优先级,react
在比照节点差别的时候会占用肯定的 js 执行工夫,Scheduler
外部借助 MessageChannel
实现了在浏览器绘制之前指定一个工夫片,如果 react
在指定工夫内没执行完差别的比照,Scheduler
就会强制交出执行权给浏览器
工夫切片
在浏览器的一帧中 js 的执行工夫如下
requestIdleCallback
是在浏览器重绘重排之后,如果还有闲暇就能够执行未实现的工作,所以为了不影响重绘重排,能够在浏览器在 requestIdleCallback
中执行耗性能的计算,然而因为 requestIdleCallback
存在兼容和触发机会不稳固的问题,scheduler
中采纳 MessageChannel
来实现 requestIdleCallback
,如果以后环境不反对MessageChannel
就采纳setTimeout
。
在 performUnitOfWork
(render
阶段的终点)之后会执行 render 阶段和 commit
阶段,如果在浏览器的一帧中,cup
的计算还没实现,就会让出 js 执行权给浏览器,这个判断在 workLoopConcurrent
函数中,shouldYield
就是用来判断残余的工夫有没有用尽。在源码中每个工夫片时 5ms,这个值会依据设施的 fps
调整。
function workLoopConcurrent() {while (workInProgress !== null && !shouldYield()) {// 如果 fiber 链还没遍历完,没有被暂停或者打断
performUnitOfWork(workInProgress);// 执行 render 阶段
}
}
function forceFrameRate(fps) {// 计算工夫片
if (fps < 0 || fps > 125) {console['error'](
'forceFrameRate takes a positive int between 0 and 125,' +
'forcing frame rates higher than 125 fps is not supported',
);
return;
}
if (fps > 0) {yieldInterval = Math.floor(1000 / fps);
} else {yieldInterval = 5;// 工夫片默认 5ms}
}
工作的暂停
在 shouldYield
函数中有一段,所以能够晓得,如果以后工夫大于工作开始的工夫 +yieldInterval
,就打断了工作的进行。
//deadline = currentTime + yieldInterval,deadline 是在 performWorkUntilDeadline 函数中计算出来的
if (currentTime >= deadline) {
//...
return true
}
相干参考视频解说:进入学习
调度优先级
在 Scheduler
中有两个函数能够创立具备优先级的工作
runWithPriority
:以一个优先级执行callback
,如果是同步的工作,优先级就是ImmediateSchedulerPriority
function unstable_runWithPriority(priorityLevel, eventHandler) {switch (priorityLevel) {// 5 种优先级
case ImmediatePriority:
case UserBlockingPriority:
case NormalPriority:
case LowPriority:
case IdlePriority:
break;
default:
priorityLevel = NormalPriority;
}
var previousPriorityLevel = currentPriorityLevel;// 保留以后的优先级
currentPriorityLevel = priorityLevel;//priorityLevel 赋值给 currentPriorityLevel
try {return eventHandler();// 回调函数
} finally {currentPriorityLevel = previousPriorityLevel;// 还原之前的优先级}
}
-
scheduleCallback
:以一个优先级注册callback
,在适当的机会执行,因为波及过期工夫的计算,所以scheduleCallback
比runWithPriority
的粒度更细。- 在
scheduleCallback
中优先级意味着过期工夫,优先级越高 priorityLevel 就越小,过期工夫离以后工夫就越近,var expirationTime = startTime + timeout;
例如IMMEDIATE_PRIORITY_TIMEOUT=-1
,那var expirationTime = startTime + (-1);
就小于以后工夫了,所以要立刻执行。 scheduleCallback
调度的过程用到了小顶堆,所以咱们能够在O(1)
的复杂度找到优先级最高的task
,不理解能够查阅材料,或者查阅我的 leetcode 算法精讲系列,在源码中小顶堆寄存着工作,每次peek
都能取到离过期工夫最近的task
。-
scheduleCallback
中,未过期工作task
寄存在timerQueue
中,过期工作寄存在taskQueue
中。 新建
newTask
工作之后,判断newTask
是否过期,没过期就退出timerQueue
中,如果此时taskQueue
中还没有过期工作,timerQueue
中离过期工夫最近的 task 正好是newTask
,则设置个定时器,到了过期工夫就退出taskQueue
中。 当
timerQueue
中有工作,就取出最早过期的工作执行。
- 在
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 越小
timeout = IMMEDIATE_PRIORITY_TIMEOUT;//-1
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;//250
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 = {// 新建 task
id: taskIdCounter++,
callback// 回调函数
priorityLevel,
startTime,// 开始工夫
expirationTime,// 过期工夫
sortIndex: -1,
};
if (enableProfiling) {newTask.isQueued = false;}
if (startTime > currentTime) {// 没有过期
newTask.sortIndex = startTime;
push(timerQueue, newTask);// 退出 timerQueue
//taskQueue 中还没有过期工作,timerQueue 中离过期工夫最近的 task 正好是 newTask
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {if (isHostTimeoutScheduled) {cancelHostTimeout();
} else {isHostTimeoutScheduled = true;}
// 定时器,到了过期工夫就退出 taskQueue 中
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);// 退出 taskQueue
if (enableProfiling) {markTaskStart(newTask, currentTime);
newTask.isQueued = true;
}
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);// 执行过期的工作
}
}
return newTask;
}