【Vue原理】Mixins - 源碼版

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

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

【Vue原理】Mixins - 源碼版 學習

今天探索的是 mixins 的源碼,mixins 根據不一樣的選項類型會作不一樣的處理測試

篇幅會有些長,你知道的,有不少種選項類型的嘛,但不是很難。只是涉及源碼不免會有些煩,this

不過這篇文章也不是給你直接看的,是爲了可讓你學習源碼的時候提供微薄幫助而已3d

若是不想看源碼的,能夠看個人白話版code

【Vue原理】Mixin - 白話版 component

咱們也是要帶着兩個問題開始對象

一、何時開始合併blog

二、怎麼合併

若是你以爲排版難看,請點擊下面原文連接 或者 關注公衆號【神仙朱】


何時合併

合併分爲兩種

一、全局mixin 和 基礎全局options 合併

這個過程是先於你調用 Vue 時發生的,也是必須是先發生的。這樣mixin 才能合併上你的自定義 options

Vue.mixin = function(mixin) {
    this.options = mergeOptions(
        this.options, mixin
    );
    return this
};

基礎全局options 是什麼?

就是 components,directives,filters 這三個,一開始就給設置在了 Vue.options 上。因此這三個是最早存在全局options

Vue.options = Object.create(null);

['component','directive','filter'].forEach(function(type) {
    Vue.options[type + 's'] = Object.create(null);
});

這一步,是調用 Vue.mixin 的時候就立刻合併了,而後這一步完成 之後,舉個栗子

公衆號

全局選項就變成下面這樣,而後每一個Vue實例都須要和這全局選項合併 公衆號

二、全局options和 自定義options合併

在調用Vue 的時候,首先進行的就是合併

function Vue(options){
    vm.$options = mergeOptions(
        { 全局component,
                 全局directive,
                 全局filter 等....},
        options , vm
    );

    // ...處理選項,生成模板,掛載DOM 等....
}

options 就是你本身傳進去的對象參數,而後跟 全局options 合併,全局options 是哪些,也已經說過了

公衆號


怎麼合併

上面的代碼一直出現一個函數 mergeOptions,他即是合併的重點

來看源碼

一、mergeOptions

function mergeOptions(parent, child, vm) {    

    // 遍歷mixins,parent 先和 mixins 合併,而後在和 child 合併
    if (child.mixins) {        

        for (var i = 0, l = child.mixins.length; i < l; i++) {
            parent = mergeOptions(parent, child.mixins[i], vm);
        }
    }    
    
    var options = {}, key;    

    // 先處理 parent 的 key,
    for (key in parent) {
        mergeField(key);
    }    

    // 遍歷 child 的key ,排除已經處理過的 parent 中的key
    for (key in child) {        
        if (!parent.hasOwnProperty(key)) {
            mergeField(key);
        }
    }    

    // 拿到相應類型的合併函數,進行合併字段,strats 請看下面
    function mergeField(key) {    

        // strats 保存着各類字段的處理函數,不然使用默認處理
        var strat = strats[key] || defaultStrat;    

        // 相應的字段處理完成以後,會完成合並的選項
        options[key] = strat(parent[key], child[key], vm, key);
    }    
    return options
}

這段代碼看上去有點繞,其實無非就是

一、先遍歷合併 parent 中的key,保存在變量options

二、再遍歷 child,合併補上 parent 中沒有的key,保存在變量options

三、優先處理 mixins ,可是過程跟上面是同樣的,只是遞歸處理而已

在上面實例初始化時的合併, parent 就是全局選項,child 就是組件自定義選項,由於 parent 權重比 child 低,因此先處理 parent 。

「公司開除程序猿,也是先開始做用較低。。」

重點其實在於 各式各樣的處理函數 strat,下面將會一一列舉

二、defaultStrats

這段函數言簡意賅,意思就是優先使用組件的options

組件options>組件 mixin options>全局options

var defaultStrats= function(parentVal, childVal) {        
    return childVal === undefined ?        
            parentVal :
            childVal
};

三、data

咱們先默認 data 的值是一個函數,簡化下源碼 ,可是其實看上去仍是會有些複雜

不過咱們主要了解他的工做過程就行了

一、兩個data函數 組裝成一個函數

二、合併 兩個data函數執行返回的 數據對象

strats.data = function(parentVal, childVal, vm) {    

    return mergeDataOrFn(
        parentVal, childVal, vm
    )
};

function mergeDataOrFn(parentVal, childVal, vm) {    

    return function mergedInstanceDataFn() {        

        var childData = childVal.call(vm, vm) 

        var parentData = parentVal.call(vm, vm)        

        if (childData) {            

            return mergeData(childData, parentData)

        } else {            

            return parentData
        }
    }
}

function mergeData(to, from) {    

    if (!from) return to    

    var key, toVal, fromVal;    

    var keys = Object.keys(from);   

    for (var i = 0; i < keys.length; i++) {

        key = keys[i];
        toVal = to[key];

        fromVal = from[key];    

        // 若是不存在這個屬性,就從新設置
        if (!to.hasOwnProperty(key)) {
            set(to, key, fromVal);
        }      

        // 存在相同屬性,合併對象
        else if (typeof toVal =="object" && typeof fromVal =="object) {
            mergeData(toVal, fromVal);
        }
    }    
    return to
}

四、生命鉤子

把全部的鉤子函數保存進數組,重要的是數組子項的順序

順序就是這樣

[    
    全局 mixin - created,
    組件 mixin-mixin - created,
    組件 mixin - created,
    組件 options - created
]

因此當數組執行的時候,正序遍歷,就會先執行全局註冊的鉤子,最後是 組件的鉤子

function mergeHook(parentVal, childVal) {    

    var arr;

    arr = childVal ?  

        // concat 不僅能夠拼接數組,什麼均可以拼接
        ( parentVal ?  
            // 爲何parentVal 是個數組呢

            // 由於不管怎麼樣,第一個 parent 都是{ component,filter,directive}
            // 因此在這裏,合併的時候,確定只有 childVal,而後就變成了數組
            parentVal.concat(childVal) : 

            ( Array.isArray(childVal) ? childVal: [childVal] )
        ) :
        parentVal  

    return arr

}

strats['created'] = mergeHook;
strats['mounted'] = mergeHook;
// ... 等其餘鉤子

五、component、directives、filters

我一直以爲這個是比較好玩的,這種類型的合併方式,我是歷來沒有在項目中使用過的

原型疊加

兩個對象並無進行遍歷合併,而是把一個對象直接當作另外一個對象的原型

這種作法的好處,就是爲了保留兩個相同的字段且能訪問,避免被覆蓋

學到了學到了.....反正我是學到了

strats.components=
strats.directives=

strats.filters = function mergeAssets(
    parentVal, childVal, vm, key
) {    
    var res = Object.create(parentVal || null);    

    if (childVal) { 
        for (var key in childVal) {
            res[key] = childVal[key];
        }   
    } 
    return res
}

就是下面這種,層層疊加的原型

公衆號

六、watch

watch 的處理,也是合併成數組,重要的也是合併順序,跟 生命鉤子同樣

這樣的鉤子

[    
    全局 mixin - watch,
    組件 mixin-mixin - watch,
    組件 mixin - watch,
    組件 options - watch
]

按照正序執行,最後執行的 必然是組件的 watch

strats.watch = function(parentVal, childVal, vm, key) { 

    if (!childVal) {        
        return Object.create(parentVal || null)
    }    

    if (!parentVal)  return childVal

    var ret = {};    

    // 複製 parentVal 到 ret 中
    for (var key in parentVal) {
       ret[key] = parentVal[key];
    }    

    for (var key$1 in childVal) {        

        var parent = ret[key$1];        
        var child = childVal[key$1];        

        if (!Array.isArray(parent)) {
            parent = [parent];
        }
        ret[key$1] = parent ? parent.concat(child) : 
                ( Array.isArray(child) ? child: [child] );

    }    
    return ret
};

七、props、computed、methods

這幾個東西,是不容許重名的,合併成對象的時候,不是你死就是我活

重要的是,以誰的爲主?必然是組件options 爲主了

好比

組件的 props:{ name:""}

組件mixin 的 props:{ name:"", age: "" }

那麼 把兩個對象合併,有相同屬性,組件的 name 會替換 mixin 的name

trats.props = 
strats.methods = 
strats.inject = 

strats.computed = function(parentVal, childVal, vm, key) {    

    if (!parentVal) return childVal

    var ret = Object.create(null);   


    // 把 parentVal 的字段 複製到 ret 中
    for (var key in parentVal) {
       ret[key] = parentVal[key];
    }    

    if (childVal) {        
        for (var key in childVal) {
           ret[key] = childVal[key];
        }
    }    

    return ret

};

其實在白話版裏面,就已經測試了不少例子,整個執行的流程也描述很清楚了,這裏就是放個源碼供參考

公衆號

相關文章
相關標籤/搜索