$nextTick

这里就涉及到 Vue 一个很重要的概念:异步更新队列(JS运行机制 、 事件循环)

  • vue 在观察到数据变化时并不是直接更新 DOM, 而是开启一个队列,并缓冲在同一事件循环(Event Loop)中发生的所有数据改变
  • 在缓冲时会去除重复数据,从而避免不必要的 DOM 操作
  • 然后在下一个事件循环中,vue 刷新队列并执行实际(已去重的)工作
  • $nextTick 是在下次 DOM 更新循环结束之后《执行延迟回调》
  • 在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM
<template>
  <div id="app">
    <button ref="btn" @click="handleClick">{{msg}}</button>
  </div>
</template>
export default {
  name: 'app',
  data () {
    msg: '旧的数据'
  },
  methods: {
    handleClick () {
      this.msg = '点击后新的数据'
      console.log(this.$refs.btn.innerHTML)     // 旧的数据
      this.$nextTick(() => {
        console.log(this.$refs.btn.innerHTML)     // 点击后新的数据
      })
    }
  }
}

$nextTick的实现

var nextTick = (function(){
  var callbacks = []    // 存储需要触发的回调函数
  // false: 允许触发在下次事件循环触发callbacks中的回调,
  // true:  已经触发过,需要等到下次事件循环
  var pending=false     // 是否正在等待的标志
  var timerFunc         // 设置在下次事件循环触发callbacks的触发函数
  //处理callbacks的函数
  function nextTickHandler() {
    pending=false                       // 可以触发timeFunc
    var copies = callbacks.splice(0)    // 复制callback
    callbacks.length = 0                // 清除callback

    for (var i=0; i<copies.length; i++) {
      copies[i]   // 触发callback的回调函数
    }
  }

  // 如果支持promise,使用promise实现
  if (typeof Promise !== 'undefined' && isNative(promise)) {
    var p = Promise.resolve()
    var logError = function(err) { console.log(err) }
    timerFunc = function() {
      p.then(nextTickHandler).catch(logError)
      // iOS的webview下,需要强制刷新队列,执行上面的回调函数
      if(isIOS) { setTimeout(noop) }
    }
    // 如果Promise不支持,但支持MutationObserver
    // H5新特性,异步,当dom变动是触发,注意是所有的dom都改变结束后触发
  } else if (typeof MutationObserver !== 'undefined' && (isNative(MutationObserver || MutationObserver.toString()==='[object MutationObserverConstructor]'))) {
    var counter = 1
    var observer = new MutationObserver(nextTickHandler)
    var textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
      characterData: true
    })
    timerFunc=function () {
      counter = (counter + 1) % 2;
      textNode.data = String(counter)
    }
  } else {
    //上面两种都不支持,用setTimeout
    timerFunc = function () {
      setTimeout(nextTickHandler, 0)
    }
  }
  // nextTick接收的函数,参数1:回调函数 参数2:回调函数的执行上下文
  return function queueNextTick(cb, ctx) {  // 用于接收触发Promise.then中回调的函数, 向回调函数中pushcallback
    var _resolve
    callbacks.push(function(){
      // 如果有回调,执行回调函数
      if (cb) { cb.call(ctx) }
      //触发Promise的then回调
      if (_resove) { _resolve(ctx) }
    })
    // 是否执行刷新callback队列
    if(!pending){
      pending = true
      timerFunc()
    }
    // 如果没有传递回调函数,并且当前浏览器支持promise,使用promise实现
    if(!cb && typeof  Promise !=='undefined'){
      return new Promise(function (resolve) {
        _resolve = resolve
      })
    }
  }
})
Last Updated: 6/30/2019, 11:36:49 AM