01. queueJob
首先需要知道这个接口的定义,他就是任务调度的 job 定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| export interface SchedulerJob extends Function { id?: number pre?: boolean active?: boolean computed?: boolean
allowRecurse?: boolean
ownerInstance?: ComponentInternalInstance }
|
然后下面是入口, 传入一个 job, 并且 push 进入队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export function queueJob(job: SchedulerJob) { if ( !queue.length || !queue.includes( job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex ) ) { if (job.id == null) { queue.push(job) } else { queue.splice(findInsertionIndex(job.id), 0, job) } queueFlush() } }
|
const queue: SchedulerJob[] = [] 这是一个 SchedulerJob 数组.
job.id = instance.uid 也就是 Vue 实例的唯一ID
02. queueFlush
1 2 3 4 5 6
| function queueFlush() { if (!isFlushing && !isFlushPending) { isFlushPending = true currentFlushPromise = resolvedPromise.then(flushJobs) } }
|
这里有两个变量
1 2
| let isFlushing = false let isFlushPending = false
|
这两个标志位主要是为了保证,一次性只执行一个更新循环,当没有循环.
设置 isFlushPending = true,然后设置微任务
到这里我们其实已经可以知道他的大概调度流程.
当触发更新,调用 queueJob => 如果没有重复 => queue.push => queueFlush => 如果没有执行 => 加入微任务(flushJobs)
这样一个流程说明了什么?
首先 queueJob 需要去重,include, 位置判断是基于是否允许递归调用。
也就是说,如果你
1 2 3
| for(let i=0;i<1000;i++) { count.value++ }
|
根本不会增加队列中 job, 他始终保证了只有一个。
第二个点就是微任务, 我之前写了一个文章,浏览器事件循环总结。
1 2
| const resolvedPromise = Promise.resolve() as Promise<any> resolvedPromise.then(flushJobs)
|
这里就是把 flushJobs 当做微任务来执行,也就是说,需要主线程的代码执行完毕以后,再执行。
1 2
| isFlushPending = true currentFlushPromise = resolvedPromise.then(flushJobs)
|
当这个代码执行,就是一次更新循环,js主线程执行完毕以后,开始执行队列,flushJobs,这个就是所谓的操作合并。
这里明白了这两个点,其实就已经大概明白了事件调度。
03. flushJobs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| function flushJobs(seen?: CountMap) { isFlushPending = false isFlushing = true if (__DEV__) { seen = seen || new Map() }
queue.sort(comparator)
const check = __DEV__ ? (job: SchedulerJob) => checkRecursiveUpdates(seen!, job) : NOOP
try { 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 = 0 queue.length = 0
flushPostFlushCbs(seen)
isFlushing = false currentFlushPromise = null if (queue.length || pendingPostFlushCbs.length) { flushJobs(seen) } } }
|
其实这里在注释里面已经说的很明确了,在这里可以开始讲一讲关于 isFlushing & isFlushPending.
isFlushPending = true 表示,flushJobs 加入微任务以后 => 开始执行这一个区间, 其他为 false.
isFlushing = true 表示 flushJobs 正在执行的区间
还有一个需要注意的点 queue.sort(comparator), 为什么需要排序,以及 id 的意义,在注释中都解释了
然后遍历队列,全部执行。
然后执行回调 flushPostFlushCbs
然后开始继续执行 flushJobs(seen),因为在执行之前的 queue 的时候,可能也有需要插入队列。
04. flushPostFlushCbs
简单说就是执行回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| export function flushPostFlushCbs(seen?: CountMap) { if (pendingPostFlushCbs.length) { const deduped = [...new Set(pendingPostFlushCbs)] pendingPostFlushCbs.length = 0
if (activePostFlushCbs) { activePostFlushCbs.push(...deduped) return }
activePostFlushCbs = deduped if (__DEV__) { seen = seen || new Map() }
activePostFlushCbs.sort((a, b) => getId(a) - getId(b))
for ( postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++ ) { if ( __DEV__ && checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex]) ) { continue } activePostFlushCbs[postFlushIndex]() } activePostFlushCbs = null postFlushIndex = 0 } }
|
这些回调函数是在 pendingPostFlushCbs 当中, 他可能是 queuePostRenderEffect 加入的, queuePostRenderEffect 是各种生命周期函数加入的。
05. 总结。
这里解释了 Vue3.x 调度的大概流程,我之所以想看这个,是因为我在看 nextTick 源码, 不看这个无法完全理解 nextTick.