【Vue原理】依賴收集 - 源碼版之引用數據類型

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

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

【Vue原理】依賴收集 - 源碼版之引用數據類型 app

上一篇,咱們已經分析過了 基礎數據類型的 依賴收集學習

【Vue原理】依賴收集 - 源碼版之基本數據類型 this

這一篇內容是針對 引用數據類型的數據的 依賴收集分析,由於引用類型數據要複雜些,必須分開寫prototype

文章很長,高能預警,作好準備耐下心好,確定仍是有點收穫的code

可是兩個類型的數據的處理,又有不少重複的地方,因此打算只寫一些差別性的地方就行了,不然顯得廢話不少server

兩個步驟,都有不一樣的地方對象

一、數據初始化blog

二、依賴收集


數據初始化流程

若是數據類型是引用類型,須要對數據進行額外的處理。

處理又分了 對象 和 數組 兩種,會分開來說

1對象

一、遍歷對象的每一個屬性,一樣設置響應式,假設屬性都是基本類型,處理流程跟上一篇同樣

二、每一個數據對象會增長一個 ob 屬性

好比設置一個 child 的數據對象

image

下圖,你能夠看到 child 對象處理以後添加了一個 ob 屬性

image

ob_ 屬性有什麼用啊?

你能夠觀察到,ob 有一個 dep 屬性,這個 dep 是否是有點屬性,是的,在上一篇基礎數據類型中講過

那麼這個 ob 屬性有什麼用啊?

你能夠觀察到,ob 有一個 dep 屬性,這個 dep 是否是有點屬性,是的,在上一篇基礎數據類型中講過

dep 正是存儲依賴的地方

好比 頁面引用了 數據child,watch 引用了數據child,那麼child 就會把這個兩個保存在 dep.subs 中

dep.subs = [ 頁面-watcher,watch-watcher ]

可是,在上一篇基礎類型種, dep 是做爲閉包存在的啊,並非保存在什麼【ob.dep】 中啊

沒錯,這就是 引用類型 和 基礎類型的區別了

基礎數據類型,只使用 【閉包dep】 來存儲依賴

引用數據類型,使用 【閉包dep】 和 【 ob.dep】 兩種來存儲依賴

什麼?你說閉包dep 在哪裏?好吧,在 defineReactive 的源碼中,你去看看這個方法的源碼,下面有

那麼,爲何,引用類型須要 使用__ob__.dep 存儲依賴呢?

首先,明確一點,存儲依賴,是爲了數據變化時通知依賴,因此 ob.dep 也是爲了變化後的通知

閉包 dep 只存在 defineReactive 中,其餘地方沒法使用到,因此須要保存另一個在其餘地方使用

在其餘什麼地方會使用呢?

在Vue掛載原型上的方法 set 和 del 中,源碼以下

function set(target, key, val) {    

    var ob = (target).__ob__;    

    // 通知依賴更新
    ob.dep.notify();
}
Vue.prototype.$set = set;
function del(target, key) {    

    var ob = (target).__ob__;    

    delete target[key];    

    if (!ob)  return

    // 通知依賴更新
    ob.dep.notify();

}
Vue.prototype.$delete = del;

這兩個方法,你們應該都用過,爲了給對象動態 添加屬性和 刪除屬性

可是若是直接添加屬性或者刪除屬性,Vue 是監聽不到的,好比下面這樣

child.xxxx=1

delete child.xxxx

因此必需要經過 Vue 包裝過的方法 set 和 del 來操做

在 set 和 del 執行完,是須要通知依賴更新的,可是我怎麼通知?

此時,【ob.dep】 就發揮做用了!就由於依賴多收集了一份在 ob.dep 中

使用就是上面一句話,通知更新

ob.dep.notify();

二、數組

一、須要遍歷數組,可能數組是對象數組,以下面

[{name:1},{name:888}]

遍歷時,若是遇到子項是對象的,會跟上面解析對象同樣操做

二、給數組保存一個 ob 屬性

好比設置一個 arr 數組

公衆號

看到 arr數組 加多了一個 ob 屬性

公衆號

其實這個 ob 屬性 和 上一段講對象 的做用是差很少的,這裏也只是說 ob.dep

數組中的 ob.dep 存儲的也是依賴,給誰用呢?

給 Vue 封裝的數組方法使用,要知道要想數組變化也被監聽到,是必須使用Vue封裝的數組方法的,不然沒法實時更新

這裏舉重寫方法之一 push,其餘的還有 splice 等,Vue 官方文檔已經有過說明

var original = Array.prototype.push;

Array.prototype.push = function() {    

    var args = [],

    len = arguments.length;    

    // 複製 傳給 push 等方法的參數
    while (len--) args[len] = arguments[len];

    // 執行 原方法
    var result = original.apply(this, args);    

    var ob = this.__ob__;    

    // notify change
    ob.dep.notify();    

    return resul
}

看到在執行完 數組方法以後,一樣須要通知依賴更新,也就是通知 ob.dep 中收集的依賴去更新

如今,咱們知道了,響應式數據對 引用類型作了什麼額外的處理,主要是加了一個 ob 屬性

咱們已經知道了 ob 有什麼用,如今看看源碼是怎麼添加 ob

// 初始化Vue組件的數據

function initData(vm) {    

    var data = vm.$options.data;

    data = vm._data = 

        typeof data === 'function' ? 

        data.call(vm, vm) : data || {};

    ....遍歷 data 數據對象的key ,重名檢測,合規檢測
    observe(data, true);

}

function observe(value) {    

    if (Array.isArray(value) || typeof value == "object") {
        ob = new Observer(value);
    }    
    return ob
}
function Observer(value) {   

    // 給對象生成依賴保存器
    this.dep = new Dep();   

    // 給 每個對象 添加一個  __ob__ 屬性,值爲 Observer 實例
    value.__ob__ = this

    if (Array.isArray(value)) { 

        // 遍歷數組,每一項都須要經過 observe 處理,若是是對象就添加 __ob__
        for (var i = 0, l =value.length; i < l; i++) {
            observe(value[i]);
        }

    } else {        

        var keys = Object.keys(value);     

        // 給對象的每個屬性設置響應式
        for (var i = 0; i < keys.length; i++) {
            defineReactive(value, keys[i]);
        }
    }
};

源碼的流程跟上一篇差很少,只是處理引用數據類型會增長多幾行源碼的額外處理

咱們以前只說了一種對象數據類型,好比下面這樣

公衆號

若是會嵌套多層對象呢?好比這樣,會怎麼處理

公衆號

沒錯,Vue 會遞歸處理,當遍歷屬性,使用 defineReactive 處理時,遞歸調用 observe 處理(源碼標紅加粗)

若是值是對象,那麼一樣給 值加多一個 ob

若是不是,那麼正常往下走,設置響應式

源碼以下

function defineReactive(obj, key, value) {  

    // dep 用於中收集全部 依賴個人 東西
    var dep = new Dep();    
    var val  = obj[key] 

    // 返回的 childOb 是一個 Observer 實例
    // 若是值是一個對象,須要遞歸遍歷對象
    var childOb = observe(val);    

    Object.defineProperty(obj, key, {
        get() {...依賴收集跟初始化無關,下面會講},
        set() { .... }
    });
}

畫一個流程圖,僅供參考

公衆號

哈哈哈,上面寫得好長啊,是有點,可是沒辦法,想說詳細點啊,好吧,還有一段,可是比較短一些哈哈哈,反正看完的人,我jio 得很厲害了,答應我,若是你仔細看完了,評論一下好嗎,讓我知道有人仔細看了


依賴收集流程

收集流程,就是重點關注 Object.defineProperty 設置的 get 方法了

跟 基礎類型數據 對比,引用類型的 收集方法也只是多了幾行處理,差別在兩行代碼

childOb.dep.depend,被我 簡單化爲 childOb.dep.addSub(Dep.target) dependArray(value) 能夠先看下源碼,以下

function defineReactive(obj, key, value) {    

    var dep = new Dep();    
    var val  = obj[key]    
    var childOb = observe(val);    

    Object.defineProperty(obj, key, {
        get() {            
            var value = val            
            if (Dep.target) {

                // 收集依賴進 dep.subs
                dep.addSub(Dep.target);

                // 若是值是一個對象,Observer 實例的 dep 也收集一遍依賴
                if (childOb) {
                    childOb.dep.addSub(Dep.target)          
                    if (Array.isArray(value)) {
                        dependArray(value);
                    }
                }
            }            
            return value
        }
    });
}

上面的源碼,混雜了 對象和 數組的處理,咱們分開說

一、對象

在數據初始化的流程中,咱們已經知道值是對象的話,會存儲多一份依賴在 ob.dep 中

就只有一句話

childOb.dep.depend();

數組還有另一個處理,就是

dependArray(value);

看下源碼,以下

function dependArray(value) {    

    for (var i = 0, l = value.length; i < l; i++) {        

        var e = value[i];        

        // 只有子項是對象的時候,收集依賴進 dep.subs
        e && e.__ob__ && e.__ob__.dep.addSub(Dep.target);   
     

        // 若是子項仍是 數組,那就繼續遞歸遍歷
        if (Array.isArray(e)) {
            dependArray(e);
        }
    }
}

顯然,是爲了防止數組裏面有對象,從而須要給 數組子項對象也保存一份

你確定會問,爲何子項對象也要保存一份依賴?

一、頁面依賴了數組,數組子項變化了,是否是頁面也須要更新?可是子項內部變化怎麼通知頁面更新?因此須要給子項對象也保存一份依賴?

二、數組子項數組變化,就是對象增刪屬性,必須用到Vue封裝方法 set 和 del,set 和 del 會通知依賴更新,因此子項對象也要保存

看個栗子

公衆號

頁面模板

公衆號

看到數組的數據,就存在兩個 ob

公衆號


總結

到這裏,就能夠很清楚,引用類型和 基礎類型的處理差別了

一、引用類型會多添加一個 __ob__屬性,其中包含 dep,用於存儲 收集到的依賴

二、對象使用 ob.dep,做用在 Vue 自定義的方法 set 和 del 中

三、數組使用 ob.dep,做用在 Vue 重寫的數組方法 push 等中

終於寫完了,真的好長,可是我以爲值得了

公衆號

公衆號

相關文章
相關標籤/搜索