Vue2源码【5】异步更新和nextTick的原理

nextTick这个api,并不是直接就帮你发起微任务的,只不过是把你传进来的回调加入到微任务的回调数组中,即作为一部分。

vue_next-tick

异步更新的原理

(经历响应式原理的派发更新)在每个watcher的update函数里面,主要就是 queueWatcher(this)。在同一个nextTick里面,对这些watcher去掉重复多余的。然后走nextTick(flushSchedulerQueue)。也就是尽量作为微任务的一部分去执行 flushSchedulerQueue。

  • flushSchedulerQueue
    • 一开始基于id来排序,是为了父的在子的前面处理;也为了让用户设置的 watcher 排在 render watcher的前面
    • 【核心】然后循环遍历,挨个执行 watcher.before (这里有执行 beforeUpdate钩子函数) 和 watcher.run
      • watcher.run会走一个 getAndInvoke(回调的逻辑)
        • 对于 渲染watcher,这个回调参数为空,其实走的是 watcher.get() === updateComponent回调函数,也就是patch;
        • 对于用户设置的wacther回调,才有这个回调。
        • 也就是用户设置的watch回调,和 updateComponent 回调的地位是差不多的。
      • (在 watcher.run的过程,有可能会再执行更新 queueWatcher(this))(也就是还是更新「改变数据」这个流程的watcher的数组,比如有时候是插队) 【面试的时候不用答出来】
    • 调用updated等生命周期钩子
    • 【重置数据】

总结:

派发更新就是当数据发生改变后,通知所有订阅了这个数据变化的 watcher 执行 update。 对于渲染watcher,派发更新的工作是把所有要执行 run方法 的 watcher 推入到队列中,在 nextTick函数执行的时候执行 flushSchedulerQueue,简而言之就是各个渲染watcher的回调。

nextTick的原理

定义在src/core/util/next-tick.js中。

function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) { // 并且try catch
        cb.call(ctx)
    } else if (_resolve) { _resolve(ctx) } // 这个是处理 Promise的情况的
  })
  if (!pending) {
    pending = true
    if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() }
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve })
  }
}

nextTick这个api,并不是直接就帮你发起微任务的,只不过是把你传进来的回调加入到微任务的回调数组中,即作为一部分。 当不再 pending 的时候发起一个异步回调去执行 flushCallbacks。

而 flushCallbacks 就是把 callbacks 数组逐个执行。

  • 如果是传入回调函数,都放入 callbacks,这个很好理解
  • 如果不是回调函数,就是首先返回一个Promise,然后Promise将在 flushCallbacks 执行的之后都 resolve,这样就会走 Promise.then。

效果:也就是可以通过 this.$nextTick(callback) 或者 this.$nextTick.then 拿到最新的data。

【反例】但是一定要注意这种反例: #重要
methods {
  change() {
    this.$nextTick(() => { // api+这个回调发起1个nextTick(/**/)
      console.log(this.$refs.msg.innerText) // 【1】间接获取 this.msg, 
    })
    this.msg = 'new Value' // 本身就是响应式的 【2】
    // 响应式的派发更新也能发起1个nextTick(/**/)
  }
}

首先【1】和【2】都会加入某个异步任务的待执行队列,但是因为【1】先加入队列,所以先执行,所以当时获取的"this.msg"还是旧值,而不是 new Value。

文章目录
  1. 异步更新的原理
  2. nextTick的原理
    1. 【反例】但是一定要注意这种反例: #重要