乐趣区

关于前端:vue3源码六scheduler

【vue3 源码】六、scheduler

在前文剖析 watch 的过程中,咱们晓得在 effect 的调度器中会将 job 推入不同的工作队列,以在不同机会执行 job 函数。本文将深入分析 job 的执行机会。

watch 中,会依据 flushscheduler进行不同解决。如果 flushsync,代表同步,那么 scheduler 就是 job,在依赖触发时,会间接执行job;如果schedulerpost,在依赖触发时,会调用一个 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__ 了,queuePostRenderEffectqueueEffectWithSuspense,否则是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函数接管四个参数:cbactiveQueue(激活的队列)、pendingQueue(期待中的队列)、index(一个索引),

如果 cb 是数组,会将 cb 解构放入 pendingQueue 中;否则判断是否传入 activeQueueactiveQueue中是否曾经存在 cb(如果cb.allowRecursetrue,从 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 中会顺次执行 pendingPreFlushCbsqueuependingPostFlushCbs 中的工作。

再来看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 数组。

通过前文咱们晓得最终 pendingPreFlushCbsqueuependingPostFlushCbs 会按程序顺次执行,pendingPreFlushCbs中保留的是 flush===pre 时的 jobpendingPostFlushCbs 中保留的是 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
}

在每次向 pendingPreFlushCbsqueuependingPostFlushCbs 中放入工作时,都会执行 queueFlush() 办法,queueFlush办法会更新 currentFlushPromise 为最新的 promise。所以应用nextTick 传入的函数会在 flushJobs 之后执行。

退出移动版