深入了解Vue中nexTick源码和原理

187 阅读3分钟

深入了解Vue中nexTick源码和原理

nextTick是Vue的一个核心实现,也是开发者经常用到的方法之一

nextTick$nextTick的区别

`nextTick`是当数据发生变化,更新后执行回调,全局的方法(一般不用)。
`$nextTick`是当dom发生变化,更新后执行的回调,回调的 this 自动绑定到调用它的实例上(用者居多)。

定义与理解

* 定义:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM

所以就衍生出来了这个获取更新后的DOM的Vue放。所以放在Vue.nextTick()回调函数中执行的应该是会对DOM进行操作的jsdaim

* 理解: nextTick(),是将回到函数延迟在下一次DOM更新数据后调用,简单的理解就是:当数据更新了,在DOM中渲染后,自动执行该函数

关于nextTick我们比较常见的使用方法

this.$nextTick(() => { 
    console.log('DOM更新后开始回调') 
})

挂载

此方法是在Vue的原型链上挂载上去的,只有一句话指向nextTick,第一个参数指的是要回调的函数,第二个参数为当前的实例对象

export function renderMixin (Vue: Class<Component>) {
  
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }
  //...代码有忽略
}

Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。

宏任务耗费的时间是大于微任务的,所以在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务。

源码解

let timerFunc;
/* 优先检测微任务(micro task) */
// 检测浏览器是否原生支持 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
  }
  isUsingMicroTask = true
} 
// 如果不支持Promise,则检测是否支持原生的 MutationObserver 
else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  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
} 
/* 对于宏任务(macro task) */
// 检测是否支持原生 setImmediate(高版本 IE 和 Edge 支持)
else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} 
// 以上都不支持的情况下,使用setTimeout
else {
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

以上代码中,Vue 会优先使用微任务,即会优先检测当前浏览器是否支持原生 Promise,如果不支持的话再去检测是否支持原生的 MutationObserver,如果还不支持,则表明当前浏览器不支持微任务,那么就使用宏任务。对于宏任务会优先检测是否支持原生 setImmediate,如果不支持的话就使用 setTimeout 0

执行回调队列

核心源码解

// 执行队列中的每一个回调
function flushCallbacks () {
  pending = false     // 重置异步锁
  // 防止出现nextTick中包含nextTick时出现问题,在执行回调函数队列前,提前复制备份并清空回调函数队列
  const copies = callbacks.slice(0)
  callbacks.length = 0
  // 执行回调函数队列
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

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()
  }
  // 如果没有提供回调,并且支持Promise,返回一个Promise
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}

总结

通过以上就是对 nextTick 的源码分析,我们了解到数据的变化到 DOM 的重新渲染是一个异步过程,发生在下一个 tick。当我们在实际开发中,比如从服务端接口去获取数据的时候,数据做了修改,如果我们的某些方法去依赖了数据修改后的 DOM 变化,我们就必须在 nextTick 后执行