【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 = null
let 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 = false
function 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 = null
let preFlushIndex = 0
export 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
之后执行。
发表回复