首頁  >  文章  >  web前端  >  在Vuejs中透過nextTick()實現非同步更新佇列

在Vuejs中透過nextTick()實現非同步更新佇列

亚连
亚连原創
2018-06-14 15:11:361848瀏覽

本篇文章主要介紹了淺談Vuejs中nextTick()非同步更新佇列原始碼解析,現在分享給大家,也給大家做個參考。

vue官網關於此解釋說明如下:

vue2.0裡面的深入響應式原理的非同步更新佇列

官網說明如下:

只要觀察到資料變化,Vue 將開啟一個佇列,並緩衝在同一事件循環中發生的所有資料變更。如果同一個 watcher 被多次觸發,只會一次推入到佇列中。這種在緩衝時去除重複資料對於避免不必要的計算和 DOM 操作上非常重要。然後,在下一個的事件循環「tick」中,Vue 刷新佇列並執行實際(已去重的)工作。 Vue 在內部嘗試對非同步佇列使用原生的 Promise.then 和 MutationObserver,如果執行環境不支持,會採用 setTimeout(fn, 0) 取代。

例如,當你設定 vm.someData = ‘new value' ,元件不會立即重新渲染。當刷新佇列時,元件會在事件循環佇列清空時的下一個「tick」更新。多數情況我們不需要關心這個過程,但是如果你想在 DOM 狀態更新後做點什麼,這可能會有些棘手。雖然 Vue.js 通常鼓勵開發人員沿著「資料驅動」的方式思考,避免直接接觸 DOM,但有時我們確實要這麼做。為了在資料變更之後等待 Vue 完成更新 DOM ,可以在資料變更之後立即使用 Vue.nextTick(callback) 。這樣回呼函數在 DOM 更新完成後就會呼叫。例如

原始程式碼解析

方法原型以及解析註解如下:

var nextTick = (function () {
    var callbacks = []; // 存储需要触发的回调函数
    var pending = false; // 是否正在等待的标识(false:允许触发在下次事件循环触发callbacks中的回调, true: 已经触发过,需要等到下次事件循环)
    var timerFunc; // 设置在下次事件循环触发callbacks的 触发函数

    //处理callbacks的函数
    function nextTickHandler () {
      pending = false;// 可以触发timeFunc
      var copies = callbacks.slice(0);//复制callback
      callbacks.length = 0;//清空callback
      for (var i = 0; i < copies.length; i++) {
        copies[i]();//触发callback回调函数
      }
    }

    //如果支持Promise,使用Promise实现
    if (typeof Promise !== &#39;undefined&#39; && isNative(Promise)) {
      var p = Promise.resolve();
      var logError = function (err) { console.error(err); };
      timerFunc = function () {
        p.then(nextTickHandler).catch(logError);
        // ios的webview下,需要强制刷新队列,执行上面的回调函数
        if (isIOS) { setTimeout(noop); }
      };

      //如果Promise不支持,但是支持MutationObserver(h5新特性,异步,当dom变动是触发,注意是所有的dom都改变结束后触发)
    } else if (typeof MutationObserver !== &#39;undefined&#39; && (
        isNative(MutationObserver) ||
        // PhantomJS and iOS 7.x
        MutationObserver.toString() === &#39;[object MutationObserverConstructor]&#39;
      )) {
      // use MutationObserver where native Promise is not available,
      // e.g. PhantomJS IE11, iOS7, Android 4.4
      var counter = 1;
      var observer = new MutationObserver(nextTickHandler);
      //创建一个textnode dom节点,并让MutationObserver 监视这个节点;而 timeFunc正是改变这个dom节点的触发函数
      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) {
      var _resolve;//用于接受触发 promise.then中回调的函数
      //向回调数据中pushcallback
      callbacks.push(function () {
        //如果有回调函数,执行回调函数
        if (cb) { cb.call(ctx); }
        if (_resolve) { _resolve(ctx); }//触发promise的then回调
      });
      if (!pending) {//是否执行刷新callback队列
        pending = true;
        timerFunc();
      }
      //如果没有传递回调函数,并且当前浏览器支持promise,使用promise实现
      if (!cb && typeof Promise !== &#39;undefined&#39;) {
        return new Promise(function (resolve) {
          _resolve = resolve;
        })
      }
    }
  })();

 我在註解中解釋了nextTick()函數的邏輯

上面處理回呼的三個方式的使用優先權的原因:因為Promise和MutationObserver和觸發的事件在同一個事件循環裡面(只不過是運行在微觀隊列裡面),但是setTimeout的回調函數是運行在下次時間循環裡面。

優先使用Promise的原因是MutationObserver在ios9.3.3以上版本的UIWebview中運行一段時間後就停止了。
上面程式碼的註解已經完全說明了程式碼邏輯。簡單理解:將callback 推到佇列裡面,如果還沒有執行過在下次事件循環執行觸發callback函數。

注意: 如果使用nextTick()不設定回呼函數,而是使用Promise的方式設定回呼函數,裡面this並不是指向目前的Vue實例,而是指向window(嚴格模式是undefined);
但是透過上面的分析可知:執行上下文是透過Promise.then()裡的回呼函數的第一個參數傳遞的。

nextTick()被使用的地方

1、他是全域Vue的函數,因此我們可以透過vue直接呼叫。

2、Vue系統中,用於處理dom更新的動作

Vue中有一個watcher,用來觀察資料的變化,然後更新dom。前面我們就知道Vue裡面不是每一次資料改變都會觸發更新dom,而是將這些操作都快取在一個佇列,在一個事件循環結束之後,刷新佇列,統一執行dom更新操作。

function queueWatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
      has[id] = true;
      if (!flushing) {
        queue.push(watcher);
      } else {
        // if already flushing, splice the watcher based on its id
        // if already past its id, it will be run next immediately.
        var i = queue.length - 1;
        while (i >= 0 && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(Math.max(i, index) + 1, 0, watcher);
      }
      // queue the flush
      if (!waiting) {
        waiting = true;
        nextTick(flushSchedulerQueue);
      }
    }
  }

簡單說明上面程式碼的邏輯,因為是watcher那裡的程式碼,以後會分析到。這裡nextTick()的作用,就是在這次事件循環結尾的時候刷新watcher檢查的dom更新操作。

3、局部Vue觸發$nextTick(),在dom更新後執行對應邏輯。

Vue.prototype.$nextTick = function (fn) {
  return nextTick(fn, this)// 设置nextTick回调函数的上下文环境是当前Vue实例
};

上面是renderMinxin中的一段程式碼,也就是render模組初始化的程式碼。

總結

如果不了解它的程式碼,我們會產生理解誤區。

1、nextTick()並不會重繪當前頁面,而且它也不是在頁面重繪才執行,而是在此次事件循環結束後一定會執行的。

2、此方法的觸發並不是在頁面更新完成才執行,第一條已經說了,但是為什麼能在此方法中取到更新後的數據,那是因為dom元素的屬性已經在watcher執行flush隊列的時候改變了,因此是可以在此時取得的。

證明上述觀點的實例:

h5有一個方法requestFrameAnimation(callback), 此方法的回呼是在頁面重繪之前呼叫。透過實驗,更新dom,nextTick()在此方法之前執行。

上面是我整理給大家的,希望今後對大家有幫助。

相關文章:

在Bootstrap框架裡使用treeview如何實作動態載入資料

關於網站產生章節目錄程式碼範例

詳細介紹Vue資料綁定

#

以上是在Vuejs中透過nextTick()實現非同步更新佇列的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn