简单理解vue.nextTick()

132 阅读1分钟

vue.nextTick()

参数

  • {Function} [callback]:回调函数,不传时提供promise调用
  • {Object} [context]:回调函数执行的上下文环境,不传默认是自动绑定到调用它的实例上。

作用 (官方解释)

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

// 修改数据 vm.msg = 'Hello' // DOM 还没有更新 Vue.nextTick(function () { // DOM 更新了 }) // 作为一个 Promise 使用 (2.1.0 起新增,详见接下来的提示) Vue.nextTick() .then(function () { // DOM 更新了 })

应用场景

在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中 原因是在 created() 钩子函数 执行的时候 DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的 js 代码放进 Vue.nextTick() 的回调函数中。 与之对应的就是 mounted 钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。

剖析源码

nextTick 的实现单独有一个JS文件来维护它,在src/core/util/next-tick.js

/* globals MutationObserver */

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

export let isUsingMicroTask = false

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]()
  }
}

//这里我们有使用微任务的异步延迟包装器。

//在2.5中,我们使用(宏)任务(结合微任务)。

//然而,在重新绘制之前更改状态时,它有一些微妙的问题

//(例如#6813,出-入转换)。

//此外,在事件处理程序中使用(宏)任务会导致一些奇怪的行为

//无法规避的风险(例如#7109、#7153、#7546、#7834、#8109)。

//所以我们现在在任何地方都使用微任务。

//这种权衡的一个主要缺点是存在一些场景

//微任务的优先级太高,可能会在两者之间触发

//顺序事件(例如,#4521、#6690,有变通方法)

//甚至在同一事件的冒泡之间(#6566)。
let timerFunc

//nextTick行为利用可以访问的微任务队列

//通过本地Promise。然后或MutationObserver。

//MutationObserver有更广泛的支持,但它被严重窃听

//iOS中的UIWebView>=9.3.3(在触摸事件处理程序中触发时)。它

//触发几次后完全停止工作…因此,如果是本机

//承诺可用,我们将使用它:

//*伊斯坦布尔忽略下一行,$flow禁用行*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    //在有问题的UIWebViews中,Promise。然后不会完全断裂,但是
    //它可能会陷入一种奇怪的状态,回调被推送到
    //微任务队列,但队列不会被刷新,直到浏览器
    //需要做一些其他工作,例如处理计时器。因此,我们可以
    //通过添加空计时器“强制”刷新微任务队列。
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
  isNative(MutationObserver) ||
  // PhantomJS and iOS 7.x
  MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
//如果本地Promise不可用,请使用MutationObserver,
//例如PhantomJS、iOS7、Android 4.4
//(#6466 MutationObserver在IE11中不可靠)
  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)) {
    // IOS 的UIWebView, Promise.then 回调被推入 microTask 队列,但是队列可能不会如期执行
        // 因此,添加一个空计时器强制执行 microTask
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
 //回退到setTimeout。
  timerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

// 抛出
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
    })
  }
}

以上仅为个人见解