Vue中$NextTick方法的运行原理

145 阅读2分钟

介绍NextTick 以及 应用场景

nextTick 是全局vue的一个函数,在vue系统中,用于处理 dom更新 的操作。 在vue生命周期的 created() 钩子函数进行的DOM操作要放在 Vue.nextTick() 的回调函数中,因为created()钩子函数执行的时候DOM并未进行任何渲染,所以此处一定要将DOM操作的JS代码放进Vue.nextTick()的回调函数中。而vue生命周期的 mounted() 钩子函数执行时,所有的DOM挂载和渲染都已完成,此时该钩子函数进行任何DOM操作都不会有问题。

NextTick方法的运行原理

  1. vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
  2. microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
  3. 因为浏览器和移动端兼容问题,vue不得不做了microtask向macrotask的兼容(降级)方案

NextTick 代码解析

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)
    }
  })
  // pending = false 执行callbacks里的函数
  if (!pending) {
    pending = true
    timerFunc()
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }

nextTick接收一个函数参数,并且将这个函数放入了callbacks,之后调用了 timerFunc(),前者是一个空数组,后者其实就是一个微任务。

callbacks 和 flushCallbacks

// 回调函数队列
const callbacks = []
let pending = false
function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

callbacks模拟回调函数队列

timerFunc

let timerFunc
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
  let counter = 1
  const observer = new MutationObserver(flushCallbacks)
  const textNode = document.createTextNode(String(counter))
  observer.observe(textNode, {
    characterData: true
  })
  timerFunc = () => {
    counter = (counter + 1) % 2
    textNode.data = String(counter)
  }
  isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

可以看出这边代码其实是做了四个判断,对当前环境进行不断的降级处理,尝试使用原生的Promise.then、MutationObserver和setImmediate,上述三个都不支持最后使用setTimeout;降级处理的目的都是将flushCallbacks函数放入微任务(判断1和判断2)或者宏任务(判断3和判断4),等待下一次事件循环时来执行。MutationObserver是Html5的一个新特性,用来监听目标DOM结构是否改变,也就是代码中新建的textNode;如果改变了就执行MutationObserver构造函数中的回调函数,不过是它是在微任务中执行的。

因此nextTick其实就是将回调函数放入在当前EventLoop的微任务队列。

总结

整体nextTick的代码都分析完毕了,总结一下它的整个流程就是

  1. 把回调函数放入callbacks等待执行
  2. 将执行函数放到微任务或者宏任务中
  3. 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调

这就是整个nextTick的执行逻辑