nextTick简介
背景:Vue在更新响应式状态时,最终的DOM更新不是同步生效的,是由Vue将它们缓存在一个队列中,直到下一个"tick"才一起执行。目的是确保每个组件无论发生多少次状态改变,都仅执行一次更新。
nextTick()可以在状态改变后立即使用,获取更新后的DOM
nextTick源码解析
入口文件packages/runtime-core/src/sheduler.ts
nextTick接受一个函数为参数,创建一个微任务。
调用nextTick时,执行该函数,把参数fn赋值给p.then(fn),在对列任务完成后,fn就会执行。
执行顺序是:
queueJob -> queueFlush -> flushJobs -> nextTick参数的 fn
queueJob()
该方法负责维护主任务队列,
const queue: SchedulerJob[] = []
let isFlushing = false // 是否正在执行
let isFlushPending = false // 是否正在等待执行
export function queueJob(job: SchedulerJob) {
// 判断条件:主任务队列为空 或者 有正在执行的任务且没有在主任务队列中
&& job 不能和当前正在执行任务及后面待执行任务相同
// 重复数据删除:
// - 使用Array.includes(Obj, startIndex) 的 起始索引参数:startIndex
// - startIndex默认为包含当前正在运行job的index,此时,它不能再次递归触发自身
// - 如果job是一个watch()回调函数或者当前job允许递归触发,则搜索索引将+1,以允许他递归触发自身-用户需要确保回调函数不会死循环
if (
(!queue.length ||
!queue.includes(
job,
isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
)) &&
job !== currentPreFlushParentJob
) {
// 判断当前job id 是否存在 不存在则添加到主任务队列
if (job.id == null) {
queue.push(job)
} else {
// 存在则从当前任务队列中查到位置并删除替换
queue.splice(findInsertionIndex(job.id), 0, job)
}
// 创建微任务
queueFlush()
}
}
queueFlush()
该方法负责创建微任务,等待任务队列执行
let isFlushing = false // 是否正在执行
let isFlushPending = false // 是否正在等待执行
const resolvedPromise: Promise<any> = Promise.resolve() // 微任务创建器
let currentFlushPromise: Promise<void> | null = null // 当前任务
function queueFlush() {
// 当前没有微任务
if (!isFlushing && !isFlushPending) {
// 避免在事件循环周期内多次创建新的微任务
isFlushPending = true
// 创建微任务,把 flushJobs 推入任务队列等待执行
currentFlushPromise = resolvedPromise.then(flushJobs)
}
}
flushJobs()
该方法负责处理队列任务主要逻辑如下:
- 先处理前置任务队列
- 根据
Id进行队列排序,并遍历执行队列任务,执行完毕后清空并重置队列 - 执行后置队列任务
- 如果队列没有被清空会递归调用
flushJobs清空队列
function flushJobs(seen?: CountMap) {
isFlushPending = false // 是否正在等待执行
isFlushing = true // 正在执行
if (__DEV__) {
seen = seen || new Map() // 开发环境下
}
flushPreFlushCbs(seen) // 执行前置任务队列
// 根据 id 排序队列,这是为了一下两点:
// 1. 组件更新顺序为:父到子,因为父级总是在子级前面先创建,它的渲染效果具有较小的优先级数
// 2. 如果父组件更新期间卸载了子组件,则改子组件更新将跳过
queue.sort((a, b) => getId(a) - getId(b))
// checkRecursiveUpdate 的条件使用必须在 try ... catch 块外中确定,因为 Rollup 默认会在 try-catch 中取消优化 treeshaking。
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 // 重置当前微任务为 Null
// some postFlushCb queued jobs!
// keep flushing until it drains.
// 如果主任务队列、前置和后置任务队列还有没被清空,就继续递归执行
if (
queue.length ||
pendingPreFlushCbs.length ||
pendingPostFlushCbs.length
) {
flushJobs(seen)
}
}
}
flushPreFlushCbs(),该方法负责执行前置任务队列
- 待处理前置任务队列不为空时,备份并清空前置任务队列
- 并遍历执行待处理前置队列任务,执行完毕后当前任务队列
- 如果队列没有被清空会递归调用
flushJobs清空队列
const pendingPreFlushCbs: SchedulerJob[] = []
let activePreFlushCbs: SchedulerJob[] | null = null
let preFlushIndex = 0
export function flushPreFlushCbs(
seen?: CountMap,
parentJob: SchedulerJob | null = null
) {
// 待处理前置任务队列不为空
if (pendingPreFlushCbs.length) {
currentPreFlushParentJob = parentJob
activePreFlushCbs = [...new Set(pendingPreFlushCbs)] // 待处理前置任务队列去重备份为activePreFlushCbs
pendingPreFlushCbs.length = 0 // 待处理前置任务队列重置
if (__DEV__) {
seen = seen || new Map()
}
// 遍历执行队列里的任务
for (
preFlushIndex = 0;
preFlushIndex < activePreFlushCbs.length;
preFlushIndex++
) {
if (
__DEV__ &&
checkRecursiveUpdates(seen!, activePreFlushCbs[preFlushIndex])
) {
continue
}
// 任务执行
activePreFlushCbs[preFlushIndex]()
}
// 清空当前活动的任务队列
activePreFlushCbs = null
preFlushIndex = 0
currentPreFlushParentJob = null
// 递归执行,直到清空前置任务队列
flushPreFlushCbs(seen, parentJob)
}
}
flushPostFlushCbs(),该方法负责执行后置任务队列
const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null
let postFlushIndex = 0
export function flushPostFlushCbs(seen?: CountMap) {
// 待处理后置任务队列队列不为空
if (pendingPostFlushCbs.length) {
// 待处理后置任务队列去重备份为deduped
const deduped = [...new Set(pendingPostFlushCbs)]
pendingPostFlushCbs.length = 0 // 待处理后置任务队列重置
// #1947 already has active queue, nested flushPostFlushCbs call
// 如果当前已经有活动的队列,就添加到执行队列的末尾,并返回
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
}
}
整个源码解析完成。