【vue3源码】六、scheduler
在前文剖析watch
的过程中,咱们晓得在effect
的调度器中会将job
推入不同的工作队列,以在不同机会执行job
函数。本文将深入分析job
的执行机会。
在watch
中,会依据flush
对scheduler
进行不同解决。如果flush
是sync
,代表同步,那么scheduler
就是job
,在依赖触发时,会间接执行job
;如果scheduler
是post
,在依赖触发时,会调用一个queuePostRenderEffect
函数;而默认状况,在依赖触发时,会依据以后有无组件实例,进行不同操作。
if (flush === 'sync') { scheduler = job as any } else if (flush === 'post') { scheduler = () => queuePostRenderEffect(job, instance && instance.suspense) } else { scheduler = () => { if (!instance || instance.isMounted) { queuePreFlushCb(job) } else { job() } } }
queuePostRenderEffect
export const queuePostRenderEffect = __FEATURE_SUSPENSE__ ? queueEffectWithSuspense : queuePostFlushCb
如果开启了__FEATURE_SUSPENSE__
了,queuePostRenderEffect
是queueEffectWithSuspense
,否则是queuePostFlushCb
。这里为了不便了解,咱们只看queuePostFlushCb
。
queuePostFlushCb
export function queuePostFlushCb(cb: SchedulerJobs) { queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex)}
queuePostFlushCb
接管一个SchedulerJobs
类型cb
参数,对于SchedulerJobs
的定义:
export interface SchedulerJob extends Function { // 一个标识 id?: number // 是否为激活状态 active?: boolean computed?: boolean allowRecurse?: boolean ownerInstance?: ComponentInternalInstance}export type SchedulerJobs = SchedulerJob | SchedulerJob[]
在queuePostFlushCb
外部调用了一个queueCb
办法,并传入了四个变量,其中第一个是queuePostFlushCb
的参数,而后三个是全局变量
const pendingPostFlushCbs: SchedulerJob[] = []let activePostFlushCbs: SchedulerJob[] | null = nulllet postFlushIndex = 0
接着看queueCb
queueCb
function queueCb( cb: SchedulerJobs, activeQueue: SchedulerJob[] | null, pendingQueue: SchedulerJob[], index: number) { if (!isArray(cb)) { if ( !activeQueue || !activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index) ) { pendingQueue.push(cb) } } else { pendingQueue.push(...cb) } queueFlush()}
queueCb
函数接管四个参数:cb
、activeQueue
(激活的队列)、pendingQueue
(期待中的队列)、index
(一个索引),
如果cb
是数组,会将cb
解构放入pendingQueue
中;否则判断是否传入activeQueue
或activeQueue
中是否曾经存在cb
(如果cb.allowRecurse
为true
,从index+1
处开始寻找,否则从index
处开始寻找)。最初执行一个queueFlush
函数。
queueFlush
// 以后是否有正在执行的工作let isFlushing = false// 以后是否有正在期待执行的工作let isFlushPending = falsefunction queueFlush() { if (!isFlushing && !isFlushPending) { isFlushPending = true // 正在执行的promise currentFlushPromise = resolvedPromise.then(flushJobs) }}
queueFlush
中的逻辑比较简单。如果没有正在执行的工作并且也没有正在期待中的工作,则将isFlushPending
置为true
,同时将flushJobs
放入一个微工作队列。
flushJobs
function flushJobs(seen?: CountMap) { // 将isFlushPending置为false,因为曾经进入执行工作中的状态 isFlushPending = false // 正在执行工作 isFlushing = true if (__DEV__) { seen = seen || new Map() } // 执行pendingPreFlushCbs中的工作 flushPreFlushCbs(seen) // 将queue按job.id升序 queue.sort((a, b) => getId(a) - getId(b)) const check = __DEV__ ? (job: SchedulerJob) => checkRecursiveUpdates(seen!, job) : NOOP try { // 执行queue中的工作 for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { const job = queue[flushIndex] if (job && job.active !== false) { if (__DEV__ && check(job)) { continue } callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) } } } finally { // 重置flushIndex flushIndex = 0 // 清空queue queue.length = 0 // 执行pendingPostFlushCbs中的工作 flushPostFlushCbs(seen) // 以后没有正在执行的工作 isFlushing = false currentFlushPromise = null // 执行残余工作 if ( queue.length || pendingPreFlushCbs.length || pendingPostFlushCbs.length ) { flushJobs(seen) } }}
在flushJobs
中会顺次执行pendingPreFlushCbs
、queue
、pendingPostFlushCbs
中的工作。
再来看queuePreFlushCb
。
queuePreFlushCb
const pendingPreFlushCbs: SchedulerJob[] = []let activePreFlushCbs: SchedulerJob[] | null = nulllet preFlushIndex = 0export function queuePreFlushCb(cb: SchedulerJob) { queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex)}
queuePreFlushCb
办法会将cb
放入一个pendingPreFlushCbs
数组。
通过前文咱们晓得最终pendingPreFlushCbs
、queue
、pendingPostFlushCbs
会按程序顺次执行,pendingPreFlushCbs
中保留的是flush===pre
时的job
,pendingPostFlushCbs
中保留的是flush===post
时的job
。那么queue
中是什么呢?queue
中保留的组件的更新函数。
const effect = (instance.effect = new ReactiveEffect( componentUpdateFn, () => queueJob(instance.update), instance.scope))
queueJob
export function queueJob(job: SchedulerJob) { // queue.length为0或queue中不存在job,并且job不等于currentPreFlushParentJob if ( (!queue.length || !queue.includes( job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex )) && job !== currentPreFlushParentJob ) { if (job.id == null) { queue.push(job) } else { // 替换反复id的job queue.splice(findInsertionIndex(job.id), 0, job) } queueFlush() }}
nextTick
export function nextTick<T = void>( this: T, fn?: (this: T) => void): Promise<void> { const p = currentFlushPromise || resolvedPromise // 将fn退出到一个微工作队列,它会在p执行完之后执行 return fn ? p.then(this ? fn.bind(this) : fn) : p}
在每次向pendingPreFlushCbs
、queue
、pendingPostFlushCbs
中放入工作时,都会执行queueFlush()
办法,queueFlush
办法会更新currentFlushPromise
为最新的promise
。所以应用nextTick
传入的函数会在flushJobs
之后执行。