源碼vue在實例化對象、vue子類聲明的時候會對父實例和子實例的參數使用設定好的合並策略合並父、子實例的參數。以及實例化前期、數據綁定時均有使用到合並策略合並參數。
定義合並策略的js文件路徑是:\vue-dev\src\core\util\options.js
在合並策略中對不同類型的參數使用了不同的合並策略。例如:strat.data合並data、defaultStrat合並[el、propsData和name]、mergrHook 合並生命周期的鈎子函數、mergeAssets合並[component、directives、filter]等。
這些合並策略通過入口函數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
- 合並生命周期的鈎子函數和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方法實現思路及源碼如下:
用人話總結這個合並規則就是:只有父時返回父,只有子時返回數組類型的子。父、子都存在時,將子添加在父的后面返回組合而成的數組。這也是父子均有鈎子函數的時候,先執行父的后執行子的的原因。源碼如下:
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數據,代碼邏輯如下:
源碼如下:
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這個函數。源碼如下:
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. 文章中涉及的觀點和理解均是個人的理解,如有偏頗或是錯誤還請大神指出,不吝賜教,萬分感謝~