vue源碼學習--合併策略對象mergeOptions

源碼vue在實例化對象、vue子類聲明的時候會對父實例和子實例的參數使用設定好的合併策略合併父、子實例的參數。以及實例化前期、數據綁定時均有使用到合併策略合併參數。vue

定義合併策略的js文件路徑是:\vue-dev\src\core\util\options.js數組

在合併策略中對不一樣類型的參數使用了不一樣的合併策略。例如:strat.data合併data、defaultStrat合併[el、propsData和name]、mergrHook 合併生命週期的鉤子函數、mergeAssets合併[component、directives、filter]等。ide

這些合併策略經過入口函數mergeOptions (parent, child, vm)中對合並參數對象中的不一樣屬性進行合併策略選擇。函數

 1 export function mergeOptions (
 2   parent: Object,
 3   child: Object,
 4   vm?: Component
 5 ): Object {
 6   if (process.env.NODE_ENV !== 'production') {
 7     checkComponents(child)
 8   }
 9 
10   if (typeof child === 'function') {
11     child = child.options
12   }
13 
14   normalizeProps(child, vm)//格式化prop爲基於對象的格式
15   normalizeInject(child, vm)//格式化Inject爲基於對象的格式
16   normalizeDirectives(child)//格式化directives爲對象的格式
17   const extendsFrom = child.extends
18   if (extendsFrom) {
19     parent = mergeOptions(parent, extendsFrom, vm)
20   }
21   if (child.mixins) {
22     for (let i = 0, l = child.mixins.length; i < l; i++) {
23       parent = mergeOptions(parent, child.mixins[i], vm)
24     }
25   }
26   const options = {}
27   let key
28   for (key in parent) {
29     mergeField(key)
30   }
31   for (key in child) {
32     if (!hasOwn(parent, key)) {
33       mergeField(key)
34     }
35   }
36   function mergeField (key) {
37     const strat = strats[key] || defaultStrat
38     options[key] = strat(parent[key], child[key], vm, key)
39   }
40   return options
41 }

從上面mergeField函數中能夠看出,Strats綁定處理參數中的各類數據的方法,統一在入口方法mergeOptions中被調用。源碼在定義strats的時的註釋也作了相應的說明,以下:學習

1 /**
2  * Option overwriting strategies are functions that handle
3  * how to merge a parent option value and a child option
4  * value into the final value.
5  */
6 const strats = config.optionMergeStrategies
  1. 合併生命週期的鉤子函數和props參數的方法爲mergeHook
 1 export const LIFECYCLE_HOOKS = [
 2   'beforeCreate',
 3   'created',
 4   'beforeMount',
 5   'mounted',
 6   'beforeUpdate',
 7   'updated',
 8   'beforeDestroy',
 9   'destroyed',
10   'activated',
11   'deactivated',
12   'errorCaptured'
13 ]
14 LIFECYCLE_HOOKS.forEach(hook => { 15 strats[hook] = mergeHook 16 })

 mergeHook方法實現思路及源碼以下:this

 

 用人話總結這個合併規則就是:只有父時返回父,只有子時返回數組類型的子。父、子都存在時,將子添加在父的後面返回組合而成的數組。這也是父子均有鉤子函數的時候,先執行父的後執行子的的緣由。源碼以下:spa

 1 /**
 2  * Hooks and props are merged as arrays.
 3  */
 4 function mergeHook (
 5   parentVal: ?Array<Function>,
 6   childVal: ?Function | ?Array<Function>
 7 ): ?Array<Function> {
 8   return childVal
 9     ? parentVal
10       ? parentVal.concat(childVal)
11       : Array.isArray(childVal)
12         ? childVal
13         : [childVal]
14     : parentVal
15 }

2.strats.data合併data數據,代碼邏輯以下:prototype

 

源碼以下:3d

 1 strats.data = function (
 2   parentVal: any,
 3   childVal: any,
 4   vm?: Component
 5 ): ?Function {
 6   if (!vm) {
 7     if (childVal && typeof childVal !== 'function') {
 8       process.env.NODE_ENV !== 'production' && warn(
 9         'The "data" option should be a function ' +
10         'that returns a per-instance value in component ' +
11         'definitions.',
12         vm
13       )
14 
15       return parentVal
16     }
17     return mergeDataOrFn.call(this, parentVal, childVal)
18   }
19 
20   return mergeDataOrFn(parentVal, childVal, vm)
21 }

  由源碼最後可知不管是vm存在與否最後都調用了mergeDataOrFn函數。這個函數根據vm是否存在,對parentVal和childVal作出不一樣的處理。可是不管vm存在不存在最終都會調用mergeData函數,將parentVal和childVal合併成最終值。因此介紹mergeDataOrFn函數以前先介紹mergeData這個函數。源碼以下:code

 1 function mergeData (to: Object, from: ?Object): Object {
 2   if (!from) return to
 3   let key, toVal, fromVal
 4   const keys = Object.keys(from)
 5   for (let i = 0; i < keys.length; i++) {
 6     key = keys[i]
 7     toVal = to[key]
 8     fromVal = from[key]
 9     if (!hasOwn(to, key)) {
10       set(to, key, fromVal)
11     } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
12       mergeData(toVal, fromVal)
13     }
14   }
15   return to
16 }

  用人話總結這個合併規則就是:

    1.若是from【childVal】中的某個屬性to【parentVal】中也有,保留to中的,什麼也不作

    2.若是to中沒有,將這個屬性添加到to中

    3.若是to和from中的某個屬性值都是對象,則遞歸調用,進行深度合併。

  不管vm存在不存在mergeDataOrFn最終都會調用mergeData函數,將parentVal和childVal合併成最終值。那麼接下來看mergeDataOrFn中對parentVal和childVal作了什麼處理。

  邏輯圖以下:

  源碼以下:

 1 export function mergeDataOrFn (
 2   parentVal: any,
 3   childVal: any,
 4   vm?: Component
 5 ): ?Function {
 6   if (!vm) {
 7     // in a Vue.extend merge, both should be functions
 8     if (!childVal) {
 9       return parentVal
10     }
11     if (!parentVal) {
12       return childVal
13     }
14     // when parentVal & childVal are both present,
15     // we need to return a function that returns the
16     // merged result of both functions... no need to
17     // check if parentVal is a function here because
18     // it has to be a function to pass previous merges.
19     return function mergedDataFn () {
20       return mergeData(
21         typeof childVal === 'function' ? childVal.call(this) : childVal,
22         typeof parentVal === 'function' ? parentVal.call(this) : parentVal
23       )
24     }
25   } else if (parentVal || childVal) {
26     return function mergedInstanceDataFn () {
27       // instance merge
28       const instanceData = typeof childVal === 'function'
29         ? childVal.call(vm)
30         : childVal
31       const defaultData = typeof parentVal === 'function'
32         ? parentVal.call(vm)
33         : parentVal
34       if (instanceData) {
35         return mergeData(instanceData, defaultData)
36       } else {
37         return defaultData
38       }
39     }
40   }
41 }

 3.strats.provide = mergeDataOrFn。provide使用mergeDataOrFn進行合併

4.strats.watch源碼以下:

 1 /**
 2  * Watchers.
 3  *
 4  * Watchers hashes should not overwrite one
 5  * another, so we merge them as arrays.
 6  */
 7 strats.watch = function (
 8   parentVal: ?Object,
 9   childVal: ?Object,
10   vm?: Component,
11   key: string
12 ): ?Object {
13   // work around Firefox's Object.prototype.watch...
14   if (parentVal === nativeWatch) parentVal = undefined
15   if (childVal === nativeWatch) childVal = undefined
16   /* istanbul ignore if */
17   if (!childVal) return Object.create(parentVal || null)
18   if (process.env.NODE_ENV !== 'production') {
19     assertObjectType(key, childVal, vm)
20   }
21   if (!parentVal) return childVal
22   const ret = {}
23   extend(ret, parentVal)
24   for (const key in childVal) {
25     let parent = ret[key]
26     const child = childVal[key]
27     if (parent && !Array.isArray(parent)) {
28       parent = [parent]
29     }
30     ret[key] = parent
31       ? parent.concat(child)
32       : Array.isArray(child) ? child : [child]
33   }
34   return ret
35 }

  註釋裏說了:watchers不該該重寫,應該保存在一個數組裏。這就是watch數據合併的策略核心。

    1.定義ret並讓ret得到parentVal的全部屬性。

    2.遍歷 childVal的全部屬性,若是ret(即parentVal)中也有的話,就把ret的屬性值弄成一個數組,把childVal的同名屬性值放在ret同名值得後面。若是不存在就把childVal弄成一個數組。

    3.最後都將數組的值賦值給ret,拓展ret的屬性和屬性值。

  這個策略其實就是,子組件、父組件都存在的時候,把watch相同值得方法放在一個數組裏,父前子後。

5.strats.component、strats.directive、strats.filter源碼以下:

 1 export const ASSET_TYPES = [
 2   'component',
 3   'directive',
 4   'filter'
 5 ]
 6 /**
 7  * Assets
 8  *
 9  * When a vm is present (instance creation), we need to do
10  * a three-way merge between constructor options, instance
11  * options and parent options.
12  */
13 function mergeAssets (
14   parentVal: ?Object,
15   childVal: ?Object,
16   vm?: Component,
17   key: string
18 ): Object {
19   const res = Object.create(parentVal || null)
20   if (childVal) {
21     process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
22     return extend(res, childVal)
23   } else {
24     return res
25   }
26 }
27 
28 ASSET_TYPES.forEach(function (type) {
29   strats[type + 's'] = mergeAssets
30 })

  這個合併策略的核心就是:將childVal的所有屬性經過原型委託在parentVal上。parentVal成爲了childVal的原型對象。

  因此須要查找某個component、directive、filter,首先會在childVal中查找,若是沒有就在其原型對象上查找。

  即子組件有就用子組件的,子組件沒有向上在父組件中尋找。

6.strats.props 、strats.methods 、strats.inject 、strats.computed源碼以下:

 1 /**
 2  * Other object hashes.
 3  */
 4 strats.props =
 5 strats.methods =
 6 strats.inject =
 7 strats.computed = function (
 8   parentVal: ?Object,
 9   childVal: ?Object,
10   vm?: Component,
11   key: string
12 ): ?Object {
13   if (childVal && process.env.NODE_ENV !== 'production') {
14     assertObjectType(key, childVal, vm)
15   }
16   if (!parentVal) return childVal
17   const ret = Object.create(null)
18   extend(ret, parentVal)
19   if (childVal) extend(ret, childVal)
20   return ret
21 }

  這種合併策略的特色就是子會覆蓋父。

    1.先將parentVal的全部屬性擴展給res

    2.再將childVal的全部屬性擴展給res。此時,如果parentVal和childVal擁有同名屬性的話,子的屬性就會覆蓋父的。也就是同名方法只會執行子的。

7.其餘的屬性使用的就是默認合併策略:defaultStrat。源碼以下:

1 /**
2  * Default strategy.
3  */
4 const defaultStrat = function (parentVal: any, childVal: any): any {
5   return childVal === undefined
6     ? parentVal
7     : childVal
8 }

  默認策略就是:子組件的選項不存在,纔會使用父組件的選項,若是子組件的選項存在,使用子組件自身的。

  由於是不會對parentVal和childVal進行分解的。因此默認策略通常用於合併比較簡單,不包含函數的屬性,例如el。

1 const defaultStrat = function (parentVal: any, childVal: any): any {
2 return childVal === undefined
3 ? parentVal
4 : childVal
5 }

 

注:本文章中學習的源碼版本爲vue  2.5.2. 文章中涉及的觀點和理解均是我的的理解,若有偏頗或是錯誤還請大神指出,不吝賜教,萬分感謝~

相關文章
相關標籤/搜索