nextTick

  • 定义:将回调推延到下一个 DOM 更新周期之后执行,在更改了一些数据以期待 DOM 更新后立刻应用它
  • 在理论中应用这个办法个别是用于组件更新,你须要获取更新后的数据,所以应用nextTick期待DOM更新

    import { createApp, nextTick } from 'vue'const app = createApp({  setup() {    const message = ref('Hello!')    const changeMessage = async newMessage => {      message.value = newMessage      // 这里的value是旧值      await nextTick()      // nextTick后获取的就是DOM更新后的value      console.log('Now DOM is updated')    }  }})
  • 这个api应用时相当简略,而且相当容易了解,然而为了知其意,还是要翻一下源码理解它的执行机制

    export function nextTick(this: ComponentPublicInstance | void,fn?: () => void): Promise<void> {const p = currentFlushPromise || resolvedPromisereturn fn ? p.then(this ? fn.bind(this) : fn) : p}
  • 下面是vue源码中nextTick的实现,为了搞清楚实现逻辑,就必须搞懂currentFlushPromise这个变量的含意,所以要从工作的调度机制开始剖析

任务调度

首先这个调度机制的性能在runtime-corescheduler文件

  • API

    // 这个文件会抛出以下几个API函数nextTick(){} // 将函数在工作队列清空后执行queueJob(){} // 增加工作并开始执行工作队列invalidateJob(){} // 删除工作queuePreFlushCb(){} // 增加前置回调函数并开始执行工作队列queuePostFlushCb(){} // 增加后置回调函数并开始执行工作队列flushPreFlushCbs(){} // 执行前置回调函数flushPostFlushCbs(){} // 执行后置回调函数
  • 咱们首先要晓得几个要害变量

    let isFlushing = false // 是否正在清空工作队列let isFlushPending = false // 清队工作已创立,期待清空状态const queue: SchedulerJob[] = [] // 工作队列let flushIndex = 0 // 以后正在执行的工作在工作队列中的索引
  • 而后咱们从queueJob这个函数开始

    /* 这个函数次要是将一个工作进行入队操作而后在满足条件的状况下启动queueFlush */export function queueJob(job: SchedulerJob) {  /**   * 工作可入队逻辑   * 1. 工作队列为空   * 2. 待入队工作不能存在于工作队列中(按状况剖析)   */  if (    (!queue.length ||      !queue.includes(        job,        /*           在正在清空队列且以后待入队工作是能够递归时,          阐明当前任务肯定和以后正在执行工作是同一工作,所以+1,          就是为了保障待入队工作和正在执行工作雷同,但不能和前面待执行工作雷同         */        isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex      )) &&    job !== currentPreFlushParentJob  ) {    // 二分查找工作在队列中的地位    const pos = findInsertionIndex(job)    if (pos > -1) {      queue.splice(pos, 0, job)    } else {      queue.push(job)    }    queueFlush()  }}
  • queueFlush

    function queueFlush() {  /**    清队工作创立后禁止再次创立更多的清队工作    因为在入队操作实现后,flushJobs会在一次递归中将工作队列全副清空,所以只须要一次清队工作即可   */  if (!isFlushing && !isFlushPending) {    isFlushPending = true    /*       清队工作创立胜利,并记录下以后清队工作,这个标记能够用于nextTick创立自定义函数,      阐明nextTick的执行机会是在清队工作后的,其实从这个中央就能够了解nextTick的执行原理了    */    currentFlushPromise = resolvedPromise.then(flushJobs)  }}
  • 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))  try {    // 遍历工作队列    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {      const job = queue[flushIndex]      if (job && job.active !== false) {        if (__DEV__ && checkRecursiveUpdates(seen!, job)) {          continue        }        // 执行当前任务        callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)      }    }  } finally {    // 重置当前任务索引    flushIndex = 0    // 清空工作队列    queue.length = 0    // 执行后置回调工作队列    flushPostFlushCbs(seen)      // 重置清队工作的状态    isFlushing = false    currentFlushPromise = null    /*       因为清队工作执行期间也会有工作入队,所以为了清队执行实现      就须要判断各工作队列的长度,而后递归执行    */    if (      queue.length ||      pendingPreFlushCbs.length ||      pendingPostFlushCbs.length    ) {      flushJobs(seen)    }  }}

总结

  • nextTick的执行机会是在工作队列(前置、主工作、后置)革除后的,currentFlushPromise是清队工作的promise标记
  • 工作队列执行程序:执行前置回调工作队列 -> 执行主工作队列 -> 执行后置回调工作队列