乐趣区

关于javascript:详解Vue中的nextTick

当在代码中更新了数据,并心愿等到对应的 Dom 更新之后,再执行一些逻辑。这时,咱们就会用到 $nextTick

funcion callback(){// 期待 Dom 更新,而后搞点事。}
$nextTick(callback);

官网文档对 nextTick 的解释是:

在下次 DOM 更新循环完结之后执行提早回调。在批改数据之后立刻应用这个办法,获取更新后的 DOM。

那么,Vue 是如何做的这一点的,是不是在调用批改 Dom 的 Api 之后(appendChild, textContent = “xxxxx” 诸如此类),调用了咱们的回调函数?
实际上产生了什么呢。

源码

nextTick 的实现逻辑在这个文件里:

vue/src/core/util/next-tick.js

咱们调用的 this.$nextTick 实际上是这个办法:

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {if (cb) {
      try {cb.call(ctx)
      } catch (e) {handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {_resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    timerFunc()}
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {_resolve = resolve})
  }
}

能够看到

  1. 回调函数被寄存到了一个数组里:callbacks。
  2. 如果没有传递回调函数,这个办法会返回一个 Promise,而后吧 reslove 当成回调函数放到 flushCallbacks 中。所以文档解释了把本该当成回调函数的 callbacks 放到 then 里的用法。
  3. 而后,有一个变量叫 pending,如果不在 pending 中,则执行函数 timerFunc。而且 pending 默认等于 false。
  4. flushCallbacks 这个函数会一口气执行所有回调函数。

timerFunc

timerFunc 定义在这里

能够看到 timerFunc 是在一个已 resolve 了的 Promise 的 then 中执行了 flushCallbacks.

利用了 js 事件循环的微工作的机制

所以,每当咱们调用 $nextTick,如果 pending 为 false,就会调用 timerFunc, 而后 timerFunc 会把 flushCallbacks 给塞到事件循环的队尾,期待被调用。

if (typeof Promise !== 'undefined' && isNative(Promise)) {const p = Promise.resolve()
  timerFunc = () => {p.then(flushCallbacks)
  }
}

flushCallbacks

而后在这个文件里还有一个函数叫:flushCallbacks
用来把保留的回调函数给全执行并清空。

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {copies[i]()}
}

pending

什么时候 pending 为 true 呢?

从 timerFunc 被调用到 flushCallbacks 被调用期间 pending 为 true

即一个事件循环周期

在 pending 期间退出的回调函数,会被曾经期待执行的 flushCallbacks 函数给执行。

外围机制

看完源码,发现除了利用了一个微工作的机制,和 Dom 更新一点关系都没有哇。

其实调用 nextTick 的不仅是开发者,Vue 更新 Dom 时,也用到了 nextTick。

开发者更新绑定的数据之后,Vue 就会立即调用 nextTick,把更新 Dom 的回调函数作为微工作塞到事件循环里去。

于是,在微工作队列中,开发者调用的 nextTick 的回调函数,就肯定在更行 Dom 的回调函数之后执行了。

然而问题又来了,依据浏览器的渲染机制,渲染线程是在微工作执行实现之后运行的。渲染线程没运行,怎么拿到 Dom 呢?

因为,渲染线程只是把 Dom 树渲染成 UI 而已,Vue 更新 Dom 之后,在 Dom 树里,新的 Dom 节点曾经存在了,js 线程就曾经能够拿到新的 Dom 了。除非开发者读取 Dom 的计算属性,触发了强制重流渲染线程才会打断 js 线程。

总结

  1. 首先 timerFunc 函数负责把回调函数们都丢到事件循环的队尾
  2. 而后,nextTick 函数负责把回调函数们都保存起来。
  3. 调用 nextTick 函数时会调用 timerFunc 函数
  4. Vue 更新 Dom 也会应用 nextTick,而且在开发者调用 nextTick 之前。
  5. 因为 4 中的先后关系和事件循环的队列性质,确保了开发者的 nextTick 的回调肯定在 Dom 更新之后
退出移动版