Vue nextTick 是何原理?

面試被大神問到 nextTick 回調的時候,就是掛載結束的時候,能夠獲取到準確的dom節點javascript

實現的原理是什麼?今天作一個總結回覆大神。。vue

網上找了這個問題,找到幾篇相關的文章看了以後發現和源碼的實現是不一致的,而後記錄下讀碼過程,方便之後翻閱再補充。java

 

先看vue源碼的 nextTick 方法(部分單詞藉助了百度翻譯)node

  
// 聲明公共數組,存儲nextTick回調函數
  var callbacks = [];
  var pending = false;
  // 執行timerFunc函數時執行這個回調函數,處理在執行nextTick時新增的方法
  function flushCallbacks () {
    pending = false;
    var copies = callbacks.slice(0);
    callbacks.length = 0;
    for (var i = 0; i < copies.length; i++) {
      copies[i]();
    }
  }

// 定義全局的timerFunc var timerFunc; // nextTick做爲行爲槓桿,和微任務隊列,原生本地Promise事件,或者是 // MutationObserver事件 // MutationObserver 事件有兼容性問題,在IOS >= 9.3.3 ,這個行爲會被徹底阻止,所 // 以,若是客戶端有Promise支持的時候,咱們使用Promise更好 // 優先判斷是否支持Promise if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve(); timerFunc = function () { p.then(flushCallbacks); // 此處大概的意思是:在webView中,事件Promise.then 不會徹底中斷,會陷入一種 // 奇怪的狀態,咱們須要讓微任務隊列繼續執行,因此添加一個宏任務空計時器刷新 // 微任務隊列,爲啥這樣能夠刷新請百度搜索瀏覽器eventloop if (isIOS) { setTimeout(noop); } }; isUsingMicroTask = true; // 若是是IE判斷是否支持MutationObserver } else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]' )) { // 原生Promise不被支持的時候,使用MutationObserver, // e.g. PhantomJS, iOS7, Android 4.4 // (#6466 MutationObserver is unreliable in IE11) var counter = 1; var observer = new MutationObserver(flushCallbacks); var textNode = document.createTextNode(String(counter)); observer.observe(textNode, { characterData: true }); timerFunc = function () { counter = (counter + 1) % 2; textNode.data = String(counter); }; isUsingMicroTask = true; } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { // 原生若是支持 setImmediate 仍是先考慮,比setTimeout好些 timerFunc = function () { setImmediate(flushCallbacks); }; } else { // 最後實在都不支持,再用setTimeout。 timerFunc = function () { setTimeout(flushCallbacks, 0); }; } // 這裏是 nextTick 函數具體實現,$nextTick就是它 function nextTick (cb, ctx) { var _resolve; callbacks.push(function () { if (cb) { try { cb.call(ctx); } catch (e) { handleError(e, ctx, 'nextTick'); } } else if (_resolve) { _resolve(ctx); } }); // 重點是這裏判斷,若是如今是在執行渲染結束的狀況,渲染結束了,開始調用 // 上面被賦值好的 timerFunc ,執行這個函數會 // 觸發執行 flushCallbacks 這個函數,他會遍歷執行所有的callbacks // 爲何會有那麼多的callback呢,由於nextTick每次被執行都會在callbacks中 // 推送一個事件,造成一個事件組就是 callbacks // 這裏的pending 是一個全局的變量,默認值false,在flushCallBacks裏會把 // pending = false;此處是一個鎖保證nextTick僅有一次執行。 if (!pending) { pending = true; timerFunc(); } // 若是沒有回調函數,vue會讓nextTick返回一個promise對象返回結果 if (!cb && typeof Promise !== 'undefined') { return new Promise(function (resolve) { _resolve = resolve; }) } }

看到這裏,差很少明白是在選擇一個能夠是微任務或者宏任務的事件,找到這個事件等待nextTick觸發。web

還有一部分代碼是在收集nextTick方法的回調函數面試

等待時機在有一部分代碼執行這些函數。api

 

還有一處不太明白,MutationObserver iOS5.1以前不支持,IE只有11支持,谷歌大部分支持,支持率仍是很高的。數組

 

可是如何使用這個api呢?繼續看promise

vue用到了MutationObserver,在使用中能夠觀察一部分你想觀察的節點組,瀏覽器

還能夠設置有哪些特性發生變化的時候回調函數會獲取到。

// 找到一個要去觀察的節點

var targetNode = document.getElementById('#app');

// 配置中添加須要觀察的項
var config = { attributes: true, childList: true, subtree: true };

// 節點發生變化,回調函數會被執行
var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('節點被添加或是被移除惹。');
        }
        else if (mutation.type == 'attributes') {
            console.log('這個 ' + mutation.attributeName + ' 被修改。');
        }
    }
};

// 建立一個觀察dom變化的實例,關聯到回調函數,callback是關鍵必填
var observer = new MutationObserver(callback);

// 使用實例的方法把觀察的區域和項目關聯到一塊兒
observer.observe(targetNode, config);

// 調用這個方法能夠解除觀察,
observer.disconnect();

 

這裏基本上清楚了,vue裏面是觀察了一段代碼 textnode + count 和 修改count,

若是能夠執行nextTick的時候,它就去執行改變count隨後觸發 MutationObserve的回調函數 flushCallbacks()。

 

 

看到這裏能夠了解到基本實現原理,回調事件使用了新增微任務或最差新增紅任務的方式,這個任務是在微任務隊列中的最後一個任務

是和事件隊列有關,須要肯定的就是任務執行的時機,在最後一個微任務或者第一個宏任務的時候。

這樣執行回調是最好的。

謝謝,不對的請指點。

相關文章
相關標籤/搜索