You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
exportconstImmediatePriority=1exportconstUserBlockingPriority=2exportconstNormalPriority=3exportconstLowPriority=4exportconstIdlePriority=5// Math.pow(2, 30) - 1// 0b111111111111111111111111111111varmaxSigned31BitInt=1073741823// Times out immediatelyvarIMMEDIATE_PRIORITY_TIMEOUT=-1// Eventually times outvarUSER_BLOCKING_PRIORITY_TIMEOUT=250varNORMAL_PRIORITY_TIMEOUT=5000varLOW_PRIORITY_TIMEOUT=10000// Never times outvarIDLE_PRIORITY_TIMEOUT=maxSigned31BitInt
functionflushWork(hasTimeRemaining,initialTime){// We'll need a host callback the next time work is scheduled.isHostCallbackScheduled=falseisPerformingWork=truetry{returnworkLoop(hasTimeRemaining,initialTime)}finally{currentTask=nullisPerformingWork=false}}functionworkLoop(hasTimeRemaining,initialTime){letcurrentTime=initialTime// 取优先级最高的任务currentTask=peek(taskQueue)while(currentTask!==null){if(currentTask.expirationTime>currentTime&&(!hasTimeRemaining||shouldYieldToHost())){// 如果已经到了这次调度的截止时间, 而且当前优先级最高的任务没有到过期时间, 就跳出下次继续调度break}constcallback=currentTask.callbackif(typeofcallback==='function'){currentTask.callback=nullcurrentPriorityLevel=currentTask.priorityLevelconstdidUserCallbackTimeout=currentTask.expirationTime<=currentTimeconstcontinuationCallback=callback(didUserCallbackTimeout)currentTime=getCurrentTime()if(typeofcontinuationCallback==='function'){currentTask.callback=continuationCallback}else{// 防止在callback的时候加入了更高优先级的任务if(currentTask===peek(taskQueue)){pop(taskQueue)}}}else{pop(taskQueue)}currentTask=peek(taskQueue)}// 返回是否还有任务需要执行, 还有任务就触发下次调度if(currentTask!==null){returntrue}else{returnfalse}}
functionadvanceTimers(currentTime){// Check for tasks that are no longer delayed and add them to the queue.vartimer=peek(timerQueue)while(timer!==null){if(timer.callback===null){// Timer was cancelled.pop(timerQueue)}elseif(timer.startTime<=currentTime){// Timer fired. Transfer to the task queue.pop(timerQueue)timer.sortIndex=timer.expirationTimepush(taskQueue,timer)}else{// Remaining timers are pending.return}timer=peek(timerQueue)}}
Scheduler 的作用
它实现的是一个任务调度器, 让任务可以在合适的时间执行,不阻塞渲染, 保证页面流畅
类似于浏览器自带的
requestIdleCallback
回调函数会在浏览器空闲时间执行不影响渲染虽然是空闲时间, 但是如果回调函数本身执行时间很长, 还是会阻塞渲染, 所以一般来说使用任务调度会先将执行时间长的大任务分成一个个执行时间比较短的小任务, 分散到不同的帧去调度执行
这也是
React
重构成Fiber
的原因, 因为JSX
天然的嵌套结构, 之前任务是递归执行无法将任务打断再继续, 重构成Fiber
链表结构之后, 就可以分散成以Fiber
为单位的小任务, 随时打断然后重新执行,让时间分片成为可能专业文档的做法是将普通函数重构成
generator
函数, 也可以将一个大任务分成很多小任务requestIdleCallback 用法
第一个参数是一个回调函数, 回调被调用时会有一个
deadline
对象, 通过调用deadline.timeRemaining()
可以知道还有多少空闲时间, 如果没有空闲时间, 但是还有任务可以继续执行requestIdleCallback
下一帧继续调度还有一种情况是一直没有空闲时间, 为了避免回调永远不被调用, 还可以设置第二个参数, 如果设置了 timeout, 回调超时之后
deadline.didTimeout
为true
requestIdleCallback 问题
requestIdleCallback
依赖屏幕刷新率, 且 timeRemaining 最大为 50ms,FPS 为 20 (因为这个函数是为了执行低优先级任务开发的, 50ms 是处理用户响应的理想时间, 如果需要响应动画的话, 理想时间是 10ms,动画执行的回调应该使用requestAnimationFrame
)Scheduler 实现
Scheduler
实现了类似requestIdleCallback
的功能, 在合适的时机执行回调函数, 同时告诉回调函数执行时间是否超时Scheduler
期望实现一个更加通用的浏览器任务的调度器, 并不只是一个在空闲时间执行的低优先级任务, 所以在上面的基础上增加了任务优先级, 延时任务等功能基础知识
Performance.now()
返回一个从页面加载到当前的时间,不同于 Date.now(), 它不会受到系统时间的影响
在
Scheduler
里面用来计算时间, 获取当前时间MessageChannel
上面说了任务调度器会在合适的时机去执行任务, 什么是合适的时机?
首先启动调度一定得是异步任务, 不能阻塞当前任务执行,
然后不能是微任务, 因为前面会有宏任务执行后面还有渲染, 无法知道帧的开始时间和已经执行时间, 那么最好就是在当前帧渲染之后立刻开始调度, 这样可以记录开始时间
setImmediate(cb)
: 在浏览器完成其他任务后立刻执行这个回调函数, 但是这个方法并没有成为浏览器的标准.setTimeout(cb, 0)
: 按照 HTML 规范, 嵌套深度超过 5 级的定时器, 会有最低 4ms 延迟的设定MessageChannel 用法
MessageChannel
接口允许我们创建一个新的消息通道,并通过它的两个 MessagePort 属性发送数据一个端口的
onmessage
的回调函数, 另一个端口postMessage
会在下一个宏任务立刻执行,MessageChannel
也常被用来模拟setImmediate
其实这个和
window.onmessage
和window.postMessage
的执行是一样的, 但是因为window.onmessage
还会被用户其他的程序监听,所以改用MessageChannel
PR优先队列
Scheduler
调度任务队列的时候, 每次都只是需要执行当前优先级最高的任务, 任务队列是由二叉堆实现的优先队列因为数据结构不是这里的重点, 实现略过, 感兴趣的可以去看一下源码
实现的接口:
任务优先级
Scheduler 分了 5 种任务优先级, 数字越小优先级越高, 每种优先级分别对应不同的任务超时时间, 类似于
requestIdleCallback
的timeout
, 会通过任务的过期时间(加入队列时间+超时时间)去排序优先队列的优先级, 越早过期的任务越先执行scheduleCallback
主要使用的 api, 类似于
requestIdleCallback
, 作用就是将不同优先级的任务加入到任务队列等待调度执行上面的逻辑就是怎么将任务加入到任务列表, 接下来就是实现怎么在合适的时机去执行任务队列里面的任务
schedulePerformWorkUntilDeadline
触发开始调度的函数, 创建一个消息通道, port1 监听调度函数, post2 发送消息异步开始调度
那什么时候会执行这个函数呢? 会有下面两种情况
performWorkUntilDeadline
消息通道监听的函数,会在渲染之后触发, 开始调度, 在这里记录任务队列执行的开始时间, 计算出截止时间
shouldYieldToHost
是否到达帧尾, deadline 就是上面开始调度的时候计算出来的
这个 api 类似于
requestIdleCallback
回调函数的deadline.timeRemaining() > 0
在
Scheduler
里面影响任务队列是否打断跳出, 也可以在外部自己调用, 确定当前执行的任务是否打断React
就是执行这个方法是否打断Fiber Tree
, 之后再恢复执行flushWork/workLoop
任务队列调度函数, 流程就是取当前优先级最高的任务执行, 每次执行之后检查是否到达 deadline, 没有就继续取最高优先级任务执行,不断循环
当然还会判断任务的过期时间, 如果任务过期了, 就算到达 deadline 也会将这个任务执行
unstable_cancelCallback
类似于
cancelIdleCallback
, 调用scheduleCallback
会返回当前任务取消任务执行直接将任务的回调置为 null, 调度到当前任务就不执行
不直接从任务队列删除的原因是任务队列是二叉堆, 只能删除第一个任务, 不能删除任意任务
延时任务
并不是需要马上就开始调度的任务, 类似于
setTimeout(() => scheduleCallback(callback), 2000)
,Scheduler
为了更好的时间调度, 增加一个timerQueue
队列去管理用法
scheduleCallback
的时候可以传入第三个可选参数对象{ delay: 2000 }
, 这个时候会将这个任务暂时加入到timerQueue
最后还是会找时机从
timerQueue
转移到taskQueue
去调度执行advanceTimers
这个函数就是检查所有
timerQueue
队列里面到期的任务转移到taskQueue
转移的时机
scheduleCallback
加入一个延时任务, 就直接创建一个定时器setTimeout
去检查advanceTimers
转移任务, 这里的逻辑应该是主要场景, 使用调度器内部的时间管理, 脱离 settimeout总体流程图
The text was updated successfully, but these errors were encountered: