Vue—nextTick的實現原理

使用nextTick

全局使用:

// DOM 還沒有更新
Vue.nextTick(function () {
  // DOM 更新了
})

// 作爲一個 Promise 使用
Vue.nextTick().then(function () {
    // DOM 更新了
})

實例方法:

new Vue({
  // ...
  methods: {
    // ...
    example: function () {
      // 修改數據
      this.message = 'changed'
      // DOM 還沒有更新
      this.$nextTick(function () {
        // DOM 現在更新了
        // `this` 綁定到當前實例
        this.doSomethingElse()
      })
    }
  }
})
//全局方法
Vue.nextTick = nextTick;
//實例方法
Vue.prototype.$nextTick = function (fn) {
    return nextTick(fn, this)
  };

nextTick方法:

Vue更新DOM是異步執行,只要監聽到數據變化,Vue
將開啓一個隊列,緩衝在同一事件循環中發生的所有數據變更,所有操作執行完成後再更新DOM。

在下一個事件循環tick中,Vue刷新隊列並執行實際工作。(Vue內部對異步隊列嘗試使用原生Promise.then , MutationObserver, setImmediate。如果不支持,就採用setTimeout

爲了在數據變化之後等待Vue完成更新DOM, 可以在數據變化之後立即使用Vue.nextTick(callback),這樣回調函數將在DOM更新完成後被調用

返回一個Promise對象,可以只用async/await語法

var callbacks = [];
var pending = false;

//刷新回調隊列,Vue刷新隊列並執行實際工作
function flushCallbacks () {
  pending = false;
  var copies = callbacks.slice(0);
  callbacks.length = 0;
  for (var i = 0; i < copies.length; i++) {
    copies[i](); //執行回調方法
  }
}

//是否啓用宏任務
var useMacroTask = false;
//在events的創建和更新階段調用
function withMacroTask (fn) {
  return fn._withTask || (fn._withTask = function () {
    useMacroTask = true;
    var res = fn.apply(null, arguments);
    useMacroTask = false;
    return res
  })
}

//將開啓一個隊列,緩衝在同一事件循環中發生的所有數據變更。
//微任務隊列和宏任務隊列,各自緩存一份
var microTimerFunc;
var macroTimerFunc;

//判斷是否支持setImmediate, 指定宏任務
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  macroTimerFunc = function () {
    setImmediate(flushCallbacks);
  };
} else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
  var channel = new MessageChannel();
  var port = channel.port2;
  channel.port1.onmessage = flushCallbacks;
  macroTimerFunc = function () {
    port.postMessage(1);
  };
} else {
  /* istanbul ignore next */
  macroTimerFunc = function () {
    setTimeout(flushCallbacks, 0);
  };
}

//判斷是否支持Promise, 指定微任務
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  var p = Promise.resolve();
  microTimerFunc = function () {
    p.then(flushCallbacks);
    //通過添加一個空定時器,強制刷新微任務隊列
    if (isIOS) { setTimeout(noop); }
  };
} else {
  // fallback to macro
  microTimerFunc = macroTimerFunc;
}
//nextTick方法
function nextTick (cb, ctx) {
  var _resolve;
  //回調隊列中插入cb
  callbacks.push(function () {
    if (cb) {
      try {
        cb.call(ctx); //傳入指定的上下文
      } catch (e) {
        handleError(e, ctx, 'nextTick');
      }
    } else if (_resolve) {
      _resolve(ctx);
    }
  });
  
  if (!pending) {
    pending = true;
    //根據useMacroTask的值判斷執行微任務隊列還是宏任務隊列
    if (useMacroTask) {
      macroTimerFunc();
    } else {
      microTimerFunc();
    }
  }
  // $flow-disable-line
  //如果沒有指定回調,並且支持Promise,那麼最終返回一個Promise對象
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(function (resolve) {
      _resolve = resolve;
    })
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章