schedule调度器
# 概述
schedule
调度器是 Vue3
中用于处理异步更新如某些副作用effct
、生命周期钩子函数onMounted
等的核心模块,它负责将异步更新任务添加到队列中,并适时执行更新操作。其核心实现位于packages\runtime-core\src\scheduler.ts
# 源码实现
# job
的概念
schedule
是通过队列的方式,所谓的任务job
就是要执行的更新操作,除了本身的函数外,一个job
可能还会有以下可选属性:
id
: 任务的唯一标识pre
: 是否是预更新任务computed
: 是否是计算属性allowRecurse
: 是否允许递归更新ownerInstance
: 任务所属的组件实例
# schedule
的实现
schedule
维护几个全局变量,如下:
queue
: 任务队列pendingPostFlushCbs
: 待处理的回调函数队列activePostFlushCbs
: 正在处理的回调函数队列isFlushPending
: 是否还有job
等待更新isFlushing
: 是否正在执行更新flushIndex
: 当前正在执行的job
的索引postFlushIndex
: 待处理的回调函数的索引currentFlushPromise
: 当前正在执行的Promise
RECURSION_LIMIT
: 递归更新的最大次数 // 内置 100
schedule
主要是通过queueJob
和queuePostFlushCb
两个函数来添加任务到队列中,而这两个队列有所不同
queue
: 添加的任务会按照优先级进行排序;这个队列处理的主要是需要异步执行的具体任务,如重新渲染、副作用函数等pendingPostFlushCbs
: 添加的任务会按照添加顺序进行排序;这个队列处理的是后置任务,如onUpdated
、onUnmounted
,后置任务是在主要刷新任务执行完成后才会被执行的任务
# queue
队列的添加和执行
queueJob
函数用于将任务添加到queue
队列中,首先会判断queue
队列是否为空以及任务是否已经存在于队列中,如果queue
队列不为空或者队列不存在该job
,则添加job
到queue
队列中去;添加也有一层判断,如果任务的id
为空,则添加任务到queue
队列的末尾,否则会通过splice
方法将任务插入到队列中,最后调用queueFlush
函数来执行刷新任务
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();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# queuePostFlushCb
pendingPostFlushCbs
用于暂存后置任务,参数cb
可能是一个函数或者一个数组
export function queuePostFlushCb(cb: SchedulerJobs) {
if (!isArray(cb)) {
if (
!activePostFlushCbs ||
!activePostFlushCbs.includes(
cb,
cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex
)
) {
pendingPostFlushCbs.push(cb);
}
} else {
pendingPostFlushCbs.push(...cb);
}
queueFlush();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 队列刷新
通过上面的代码可以发现,无论是存queue
队列还是pendingPostFlushCbs
队列,都会调用queueFlush
去刷新队列
function queueFlush() {
if (!isFlushing && !isFlushPending) {
isFlushPending = true;
currentFlushPromise = resolvedPromise.then(flushJobs);
}
}
2
3
4
5
6
queueFlush
是一个内部函数,通过判断isFlushing
和isFlushPengding
,来决定是否执行flushJobs
。前面提到过,isFlushing
表示是否有任务正在刷新,isFlushPending
表示是否需要刷新队列。
通过Promise.then
,将flushJobs
放入微任务队列中,等待主线程执行。
# 刷新任务
刷新任务flushJobs
是一个递归函数,如果queue
和pengdingPostFlushCbs
队列不为空,则会一直调用执行。
function flushJobs(seen3) {
isFlushPending = false;
isFlushing = true;
if (true) {
seen3 = seen3 || /* @__PURE__ */ new Map();
}
queue.sort(comparator);
const check = true ? (job) => checkRecursiveUpdates(seen3, job) : NOOP3;
try {
for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
const job = queue[flushIndex];
if (job && job.active !== false) {
if (check(job)) {
continue;
}
callWithErrorHandling(job, null, 14 /* SCHEDULER */);
}
}
} finally {
flushIndex = 0;
queue.length = 0;
flushPostFlushCbs(seen3);
isFlushing = false;
currentFlushPromise = null;
if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen3);
}
}
}
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
flushJobs
会调用comparator
方法对queue
队列进行排序,排序的依据是job
的id
,若id
相同,则按照job
的pre
进行排序,然后再调用checkRecursiveUpdates
方法来检测是否需要递归更新,最后通过for
遍历queue
调用callWithErrorHandling
方法来执行job
,执行完成后会调用flushPostFlushCbs
方法来执行后置任务。
在flushJobs
中还会调用flushPostFlushCbs
,刷新等待队列的后置任务。其实现如下:
function flushPostFlushCbs(seen3) {
if (pendingPostFlushCbs.length) {
const deduped = [...new Set(pendingPostFlushCbs)].sort(
(a, b) => getId(a) - getId(b)
);
pendingPostFlushCbs.length = 0;
if (activePostFlushCbs) {
activePostFlushCbs.push(...deduped);
return;
}
activePostFlushCbs = deduped;
if (true) {
seen3 = seen3 || /* @__PURE__ */ new Map();
}
for (
postFlushIndex = 0;
postFlushIndex < activePostFlushCbs.length;
postFlushIndex++
) {
if (checkRecursiveUpdates(seen3, activePostFlushCbs[postFlushIndex])) {
continue;
}
activePostFlushCbs[postFlushIndex]();
}
activePostFlushCbs = null;
postFlushIndex = 0;
}
}
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
flushPostFlushCbs
首先会判断等待队列pendingPostFlushCbs
的长度,若其不为空,则通过Set
过滤重复job
,再通过job
的id
进行排序,然后就是将排序后的值赋给activePostFlushCbs
,循环遍历activePostFlushCbs
,直接执行任务