vue3源码】六、scheduler
【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之后执行。
转载自:https://segmentfault.com/a/1190000042211114