轉載請註明出處 https://segmentfault.com/a/11...javascript
差很少看了快三週的 Vue 源碼,決定寫一些東西,記錄一下收穫,畢竟時間一長,很久不看總會忘的,今天就看看 optionMergeStrategies
。寫這篇文章時,Vue 已經發布了2.0.1正式版,但這裏講解的源碼是 2.0.0-rc6 ,但基本沒什麼區別。java
optionMergeStrategies
主要用於 mixin
以及 Vue.extend()
方法時對於子組件和父組件若是有相同的屬性(option)時的合併策略。segmentfault
這裏先看看默認的合併策略,畢竟以後要用到不少次的。數組
var defaultStrat = function (parentVal, childVal) { return childVal === undefined ? parentVal : childVal }
源代碼很簡單,傳入兩個參數 parentVal
, childVal
分別對應於父組件和子組件的選項,合併的策略就是,子組件的選項不存在,纔會使用父組件的選項,若是子組件的選項存在,使用子組件自身的。函數
/** * Option overwriting strategies are functions that handle * how to merge a parent option value and a child option * value into the final value. * * config.optionMergeStrategies: Object.create(null) */ // config 是一個全局對象,對應於Vue.config // config.optionMergeStrategies 初始化時是一個空對象 // config.optionMergeStrategies = Object.create(null) var strats = config.optionMergeStrategies /** * Options with restrictions */ if ("development" !== 'production') { strats.el = strats.propsData = function (parent, child, vm, key) { // 若是 vm 不存在,報錯: key屬性用在vm實例上 if (!vm) { warn( "option \"" + key + "\" can only be used during instance " + 'creation with the `new` keyword.' ) } return defaultStrat(parent, child) } strats.name = function (parent, child, vm) { if (vm && child) { warn( 'options "name" can only be used as a component definition option, ' + 'not during instance creation.' ) } return defaultStrat(parent, child) } }
上面能夠看出,el
, propsData
和 name
的合併策略就是默認的合併策略,即以子組件的選項爲主,子組件的選項不存在時,才使用父組件的。this
function mergeHook ( parentVal, childVal ) { return childVal ? parentVal // 若是 childVal存在 ? parentVal.concat(childVal) // 若是parentVal存在,直接合並 : Array.isArray(childVal) // 若是parentVal不存在 ? childVal // 若是chilidVal是數組,直接返回 : [childVal] // 包裝成一個數組返回 : parentVal // 若是childVal 不存在 直接返回parentVal } // strats中添加屬性,屬性名爲生命週期各個鉤子 config._lifecycleHooks.forEach(function (hook) { strats[hook] = mergeHook // 設置每個鉤子函數的合併策略 })
若是父組件和子組件都設置了鉤子函數選項,那麼 它們會合併到一個數組裏,並且父組件的鉤子函數會先執行,最後返回一個合併後的數組。具體見源碼裏的註釋。rest
/** * Assets // components,directives,filters * When a vm is present (instance creation), we need to do * a three-way merge between constructor options, instance * options and parent options. */ function mergeAssets (parentVal, childVal) { // parentVal: Object childVal: Object var res = Object.create(parentVal || null) // 原型委託 return childVal ? extend(res, childVal) : res } config._assetTypes.forEach(function (type) { strats[type + 's'] = mergeAssets })
對於 assets
也就是 components
, directives
, filters
合併的策略就是返回一個合併後的新對象,新對象的自有屬性所有來自 childVal
, 可是經過原型鏈委託在了 parentVal
上。code
這裏順便提提在一個對象裏查找屬性的規則。舉個例子,當查找一個屬性時,如 obj[a] ,若是 obj 沒有 a 這個屬性,那麼將會在 obj 對象的原型裏找,若是尚未,在原型的原型上找,直到原型鏈的盡頭,若是尚未找到,返回 undefined。component
所以這裏一樣一個道理,在 res 對象裏查找某個 component 或 directive , 首先會找 childVal裏的,若是沒有,纔會沿着原型鏈向上,找 parentVal中對應的屬性。事實上,和 defaultStrat 一個道理。對象
strats.props = strats.methods = strats.computed = function (parentVal, childVal) { // parentVal: Object childVal: Object if (!childVal) return parentVal if (!parentVal) return childVal var ret = Object.create(null) extend(ret, parentVal) extend(ret, childVal) // child的會覆蓋parent的 return ret }
一樣來看源碼,函數解構一樣返回一個新的 res 對象,一樣適用了 extend 方法拓展了 res 對象。可是要注意的是,先拓展的是 parentVal 對象,而後再拓展 childVal對象,這就意味着當拓展 chilidVal 對象的時候,若是 childVal中有 parentVal 的同名屬性時,將會直接覆蓋掉。這裏順便貼一下 extend 方法的源碼
/** * Mix properties into target object. */ function extend (to, _from) { for (var key in _from) { to[key] = _from[key] } return to }
/** * Watchers. * * Watchers hashes should not overwrite one * another, so we merge them as arrays. * 不該該重寫(覆蓋),應該保存在一個數組裏 */ strats.watch = function (parentVal, childVal) { /* istanbul ignore if */ if (!childVal) return parentVal if (!parentVal) return childVal var ret = {} extend(ret, parentVal) // ret首先得到parentVal的所有屬性 for (var key in childVal) { var parent = ret[key] // 子組件的某個watcher在父組件中的值 var child = childVal[key] if (parent && !Array.isArray(parent)) { parent = [parent] // 若是parent不是一個數組,將其包裝成一個數組 } ret[key] = parent ? parent.concat(child) // parent在前,child在後 : [child] // 若是在父組件中不存在,以數組的形式存儲子組件的watcher } return ret }
子組件和父組件的watchers不該該覆蓋,而是應該把它們都合併在一個數組裏。這裏一樣是父組件的在前,子組件的在後。
data 是個重頭戲,也是整個合併策略中最複雜的,這是由於,在組件中data是以函數的形式存在的。
/* * */ strats.data = function ( parentVal, childVal, vm // 若是傳入了vm,那麼它表示的是組件的根實例 ) { if (!vm) { // 若是沒傳入 // in a Vue.extend merge, both should be functions if (!childVal) { return parentVal } if (typeof childVal !== 'function') { // 在組件中定義data 必須是一個函數 "development" !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ) return parentVal // 報完錯,返回parentVal的data } if (!parentVal) { return childVal // parentVal不存在,返回 childVal的data } // when parentVal & childVal are both present, // we need to return a function that returns the // merged result of both functions... no need to // check if parentVal is a function here because // it has to be a function to pass previous merges. // 這裏返回的應該是一個函數,函數返回結果是合併後的data對象 return function mergedDataFn () { return mergeData( childVal.call(this), parentVal.call(this) ) } } else if (parentVal || childVal) { // 若是提供了vm實例 return function mergedInstanceDataFn () { // 一樣返回一個函數 // instance merge var instanceData = typeof childVal === 'function' ? childVal.call(vm) : childVal var defaultData = typeof parentVal === 'function' ? parentVal.call(vm) : undefined // 若是parentVal不是函數,則拋棄。 if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } } } }
/** * Helper that recursively merges two data objects together. * 合併規則: * 1. 若是from中的某個屬性to中有,保留to中的,什麼都不作。 * 2. 若是to中沒有,賦值。 * 3. 若是to中和from中的某個屬性值都是對象,遞歸調用。 */ function mergeData (to, from) { var key, toVal, fromVal for (key in from) { toVal = to[key] fromVal = from[key] if (!hasOwn(to, key)) { set(to, key, fromVal) // 設置to[key] = fromVal } else if (isObject(toVal) && isObject(fromVal)) { mergeData(toVal, fromVal) // 若是對應的值都是對象,則遞歸合併。 } } return to }
代碼中註釋都寫得很清楚了,這裏就很少說了。 Vue 中對於 data 屬性的合併就是執行 parentVal 和 childVal 的函數,而後再合併函數返回的對象。
以上所說的都是 Vue 自定義的合併的策略,固然你也能夠自定義某個選項的合併策略。
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) { // return mergedVal }
好比想要修改 watch的合併策略
Vue.config.optionMergeStrategies.watch = function (toVal, fromVal) { // return mergedVal }
至於傳入的函數參數,能夠參考以前講解的源碼。
全文完