解决目标对象更快完成渲染
与及时响应优先级更高任务
之间的矛盾。
Perceived performance 可感知到的性能
在流畅性的章节中提到将主线程的一个长任务进行时间分片
可以拆分为多个帧任务, 但如果同时存在多个任务则必然会存在一种竞争机制, 于是需要一种 Schedule
机制, 在时间分片中加入动态优先级
的概念来真正避免卡顿现象。
调度算法思想:
- 任务执行时间得足够短, 能在一帧时间内执行完(时间分片);
- 不同任务存在不同的优先级;
在一帧中执行的任务种类有以下几种类别:
user-blocking tasks
: 基于用户的交互的任务(可见), 需在当前帧处理; ->input
、rAF
、microtask
(顺畅的交互应小于 10 ms, 下同)default tasks
: 基于用户获取数据渲染到界面上的任务(可见), 在当前的帧以及下一帧里处理; ->macrotask
(小于 100 ms)idle tasks
: 和分析、缓存、排序相关的任务(不可见); ->requestIdleCallback
(小于 10 ms)
不能拆分的任务(执行时间较长的 chunk)怎么办?
借助 web Worker
任务的排序机制是由 expiration time
这个字段决定的,其值为 callback 的注册时间
与当前任务优先级的值
之和, 表示过期时间(值越小, 越早执行)。
优先级的值分为以下几种类别:
Immediate
: (0ms timeout)需要实时交互的任务; (Do it now)User Block
: (250ms timeout)对页面交互有副作用的任务; (Do it now)Normal
: (5s timeout)不影响交互的任务; (Do it soon)Low
: (10s timeout)可以延迟执行,但最终需要执行的任务; (Do it eventually)Idle
: (no timeout)执行与否不影响应用的任务; (Do it if you can)
在了解了 expiration time
之后, 对 Schedule
的流程进行如下概述:
- 所有回调根据
expiration time
排好序放入一个队列中; Schedule
自己注册一个回调 callback 调用该队列,并下一个帧中执行它;- 在下一帧中尽可能多地执行队形里的回调;
Schedule 中 4 个比较重要的方法的作用罗列如下:
- requestHostCallback: 提供调用下一帧的能力
- cancelHostCallback: 提供取消当前任务的能力
- shouldYieldToHost: 提供暂停当前任务的能力
- getCurrentTime: 根据该函数获取的值从而判断具体的优先级
JND(Just Noticeable Difference), JND
// Computes the next Just Noticeable Difference (JND) boundary.
// The theory is that a person can't tell the difference between small differences in time.
// Therefore, if we wait a bit longer than necessary that won't translate to a noticeable
// difference in the experience. However, waiting for longer might mean that we can avoid
// showing an intermediate loading state. The longer we have already waited, the harder it
// is to tell small differences in time. Therefore, the longer we've already waited,
// the longer we can wait additionally. At some point we have to give up though.
// We pick a train model where the next boundary commits at a consistent schedule.
// These particular numbers are vague estimates. We expect to adjust them based on research.
function jnd(timeElapsed: number) {
return timeElapsed < 120
? 120
: timeElapsed < 480
? 480
: timeElapsed < 1080
? 1080
: timeElapsed < 1920
? 1920
: timeElapsed < 3000
? 3000
: timeElapsed < 4320
? 4320
: ceil(timeElapsed / 1960) * 1960;
}
Time Slicing
is the premise of Suspense
. Because in each time slicing it can compare the task priority, and then determine whether to show the loading.
- 相比
requestIdleCallback
, 其有更简洁的 api; - 另外其不会受到优先级的限制;
- scheduling-on-off-main-thread: 讲解了如何在帧里拆分任务以及使用 worker 的一些限制
- Scheduling in React: 任务的排序机制
- main-thread-scheduling: schedule API in future. 核心是从最高优先级的任务中挑选时间过去最久的任务。
- Scheduling is the Future: @ReactEurope 2019 speaker: @aweary