寫文章不容易,點個讚唄兄弟 專一 Vue 源碼分享,文章分爲白話版和 源碼版,白話版助於理解工做原理,源碼版助於瞭解內部詳情,讓咱們一塊兒學習吧 研究基於 Vue版本 【2.5.17】數組
若是你以爲排版難看,請點擊 下面連接 或者 拉到 下面關注公衆號也能夠吧函數
今天探索的是 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,他即是合併的重點
來看源碼
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,下面將會一一列舉
這段函數言簡意賅,意思就是優先使用組件的options
組件options>組件 mixin options>全局options
var defaultStrats= function(parentVal, childVal) { return childVal === undefined ? parentVal : childVal };
咱們先默認 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; // ... 等其餘鉤子
我一直以爲這個是比較好玩的,這種類型的合併方式,我是歷來沒有在項目中使用過的
原型疊加
兩個對象並無進行遍歷合併,而是把一個對象直接當作另外一個對象的原型
這種作法的好處,就是爲了保留兩個相同的字段且能訪問,避免被覆蓋
學到了學到了.....反正我是學到了
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 的處理,也是合併成數組,重要的也是合併順序,跟 生命鉤子同樣
這樣的鉤子
[ 全局 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 };
這幾個東西,是不容許重名的,合併成對象的時候,不是你死就是我活
重要的是,以誰的爲主?必然是組件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 };
其實在白話版裏面,就已經測試了不少例子,整個執行的流程也描述很清楚了,這裏就是放個源碼供參考