【Vue原理】NextTick - 源碼版 之 服務Vue

寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】vue

若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧數組

【Vue原理】NextTick - 源碼版 之 服務Vue 異步

初次看的兄弟能夠先看 【Vue原理】NextTick - 白話版 簡單瞭解下NextTick函數

好的,今天,就來詳細記錄 Vue 和 nextTick 的那些事學習

公衆號

nextTick 在 Vue 中,最重要的就是~~~this

協助 Vue 進行更新操做!prototype

上篇文章3d

NextTick-源碼版之獨立自身 提到過,nextTick 幫助 Vue 避免頻繁的更新,這裏簡單提一下,code

每次修改數據,都會觸發數據的依賴更新component

也就是說數據被修改的時候,會調用一遍**【引用這個數據的實例】**的更新函數

那麼,按道理來講,修改3次,就應該調用3遍更新函數,可是實際上只會調用一遍

好比咱們使用 watch 監聽 data(data 便收集了 watch 的 watcher,監聽回調就是更新函數)

公衆號

結果就是隻打印一次

公衆號

至於依賴更新,能夠看下面的文章

依賴更新 - 源碼版

其實,修改數據可以只更新一次,不止是 nextTick 起了做用,Vue 也作了其餘處理,好比過濾實例,清空隊列等等,下面就來講一下

一切先從**【實例更新函數】**開始

第一個要說的就是 watcher!每一個實例都有一個 watcher,而後 watcher 保存着實例的更新函數

每一個實例都會經過 new Vue 生成的,因此會有一個專屬的 watcher

更新函數被保存在 watcher.getter 上

function Vue(){
    ....
    new Watcher(vm, 實例更新函數)
}



function Watcher(vm, expOrFn) {    

    this.getter = expOrFn;

};



Watcher.prototype.get = function() {  

    this.getter.call(vm, vm);

};

Watcher.prototype.update = function() {
    queueWatcher(this);
};
Watcher.prototype.run = function() {  

    this.get();

};

咱們知道, Vue 的 data 是響應式的,就是經過 Object.defineProperty 設置 get 和 set

當數據被修改的時候, set 函數被觸發,函數內部會通知全部的實例進行更新(就是調用每一個實例的 watcher.update 方法)

具體能夠看這個

響應式原理 - 白話版 依賴更新 - 源碼版

那麼咱們如今的重點就在 watcher.update 上了,看看上面的 Watcher 代碼

出現了一個 queueWatcher 的東西

公衆號


更新隊列

速度看源碼!

var queue = [];

var has = {};

var index = 0;

var flushing = false;

var waiting= false;



function queueWatcher(watcher) {  



    var id = watcher.id;    



    // 若是是同一個 Vue 實例,就不要重複添加了

    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 > index && queue[i].id > watcher.id) {
                i--;
            }  

         

            // 最後放在隊列中,一個比我老的 watcher 後面

            queue.splice(i + 1, 0, watcher);
        }        



        // 在 flushSchedulerQueue 執行以後設置爲 false

        if (!waiting) {
            waiting = true;
            nextTick(flushSchedulerQueue);
        }
    }
}

先說說其中涉及的幾個變量

公衆號

has

是一個對象,用來過濾watcher。

當這個watcher 已經調用過更新函數,那麼就在 has 中標記這個 id

也就是,你同時間調用屢次 watcher.update ,其實只有第一次調用有用,後面的都會被過濾掉

queue

一個數組,watcher 更新隊列,存放須要更新的 watcher

flushSchedulerQueue

watcher 更新隊列執行函數,下面有講到

waiting

爲 true 表示已經把 【watcher 更新隊列執行函數】 註冊到宏微任務上了(或者說存放進 callbacks 中)。

正在等待JS棧爲空後,就能夠執行更新。直到全部watcher 更新完畢,才重置爲 false

flushing

爲 true 表示 watcher 更新隊列正在執行更新(就是開始遍歷 watcher 隊列,逐個調用 watcher 更新了)

直到全部watcher 更新完畢,才重置爲 false

queueWatcher 源碼不算很複雜,主要作兩件事

一、處理watcher 更新隊列 queue

二、註冊 【watcher 更新隊列 執行函數】進宏微任務

處理 watcher 更新隊列 queue

當 flushing 爲 false時,表示 queue 尚未開始遍歷執行,直接 push

當 flushing 爲 true,表示 queue 已經開始遍歷,執行其中的 watcher 更新了

而後,作了一個很特殊的插入操做(爲了方便看,把上面的源碼截取了)

公衆號

我仍是沒有看懂這是爲何? 直到我看到了 flushSchedulerQueue 的 源碼!

由於在 flushSchedulerQueue 執行的時候(此時設置了 flushing = true),內部把 queue 升序排列了!

因此在 flushing 的時候,queue已是有序狀態,中途進來的 watcher,固然也要按順序來

因此,這一段的做用就是給 新來的 watcher 排序

其中 index 表示 如今正遍歷到第幾個 watcher(在 flushSchedulerQueue 中設置)

因此,也必然是排到已經執行過的 watcher 後面的(否則就遍歷不到這個watcher 了啊)

註冊 【watcher 更新隊列 執行函數】進宏微任務

已經講到 flushSchedulerQueue 了,他就是 註冊宏微任務的異步回調

公衆號

直接存放進 異步任務隊列 callbacks 中的

關於 nextTick 的 異步任務隊列 ,能夠看

NextTick - 源碼版 之 獨立自身

接下來,就看 flushSchedulerQueue


執行更新隊列

function flushSchedulerQueue() {



    flushing = true;    

    var watcher;    

    

    // 升序排列

    queue.sort(function(a, b) {        

        return a.id - b.id;

    });    

    

    for (index = 0; index < queue.length; index++) {

        watcher = queue[index];
        has[watcher.id] = null;
        watcher.run();
    }    



    // 全部watcher 完成更新,重置狀態

    queue.length = 0;
    has = {};
    waiting = flushing = false;

}

flushSchedulerQueue 的做用

一、升序排列 watcher 更新隊列

二、遍歷 watcher 更新隊列,而後逐個調用 watcher 更新

三、watcher 更新隊列執行完畢,重置狀態

其餘我都看得明白,惟獨我不懂一個問題

爲何要把 queue 按照 watcher.id 升序排列??

首先,watcher.id 越大,表示這個 watcher 越年輕,實例是越後面生成的

vue 的官方回答

This ensures that:

  1. Components are updated from parent to child. (because parent is always created before the child)

  2. A component's user watchers are run before its render watcher (because user watchers are created before the render watcher)

  3. If a component is destroyed during a parent component's watcher run, its watchers can be skipped.

我只挑一點

先更新父組件,再更新子組件(由於父組件比子組件先建立)

爲何先更新父組件,再更新子組件,我仍是想不通啊?

我的認爲,由於父組件跟子組件是有聯繫的,什麼聯繫呢?

好比 props

當 父組件傳給子組件的數據變化的時候,父組件須要把 變化後的數據 傳給 子組件,子組件才能知道數據變了

那麼 子組件才能更新組件內使用 props 的地方

因此,父組件必須先更新,把最新數據傳給 子組件,子組件再更新,此時才能獲取最新的數據

否則你子組件更新了,父組件再傳數據過來,那就不會子組件就不會顯示最新的數據了啊

至於 父組件更新時怎麼傳 數據給子組件的?

【Vue原理】Props - 白話版

最後,走個簡單流程

數據變化,通知 watcher 更新,watcher.update

queueWatcher 把 watcher 添加進 【queue 更新隊列】

把 flushSchedulerQueue 註冊進宏微任務

JS 主棧執行完,開始執行異步代碼

flushSchedulerQueue 遍歷 queue ,逐個調用 watcher 更新

完成更新

公衆號

公衆號

相關文章
相關標籤/搜索