vue中的混入,能夠在必定程度上提升代碼的複用性。通俗來講,混入相似於「繼承」,當前組件對象繼承於組件對象,通常狀況下遵循「就近原則」。可是與繼承不一樣的是,繼承通常都跟隨着屬性的重寫與合併,混入在不一樣的配置項中,有着不一樣的混入策略,下面會一一進行介紹vue不一樣配置項的混入策略。vue混入的基本流程如圖所示,混入屬性的合併是發生在組件的生命週期鉤子調用以前的。vue
在咱們實例化vue時,主要是調用Vue._init方法,此方法,主要功能是初始化組件狀態、事件,具體代碼以下:ios
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options // 合併屬性,判斷初始化的是不是組件 if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { // 合併vue屬性 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { // 初始化proxy攔截器 initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm // 初始化options initLifecycle(vm) // 初始化組件事件偵聽 initEvents(vm) // 初始化渲染方法 initRender(vm) callHook(vm, 'beforeCreate') // 初始化依賴注入內容,在初始化data、props以前 initInjections(vm) // resolve injections before data/props // 初始化props/data/method/watch/methods initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } // 掛載元素 if (vm.$options.el) { vm.$mount(vm.$options.el) } }
mergeOptions是合併組件的配置項,第一次實例化Vue時調用,接收兩個參數,第一個參數是構造函數默認自帶的屬性,在項目初始化時會調用initGlobalAPI方法,會在Vue構造函數上初始化一些默認的配置,具體代碼以下所示,第二個爲咱們實例化vue配置的屬性設計模式
// 初始化全局API export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config if (process.env.NODE_ENV !== 'production') { configDef.set = () => { warn( 'Do not replace the Vue.config object, set individual fields instead.' ) } } Object.defineProperty(Vue, 'config', configDef) // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. Vue.util = { warn, extend, mergeOptions, defineReactive } Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 2.6 explicit observable API Vue.observable = <T>(obj: T): T => { observe(obj) return obj } // 初始化Vue構造函數的options,初始屬性爲components,directives,filters Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. // _base屬性 Vue.options._base = Vue extend(Vue.options.components, builtInComponents) // 初始化vue.use api initUse(Vue) // 初始化mixins api initMixin(Vue) // 初始化extend api initExtend(Vue) // 初始化component,directive,filter initAssetRegisters(Vue) }
混入實現的主要代碼以下:api
1 export function mergeOptions ( 2 parent: Object, 3 child: Object, 4 vm?: Component 5 ): Object { 6 if (process.env.NODE_ENV !== 'production') { 7 // 檢測組件名稱是否合法 8 checkComponents(child) 9 } 10 11 if (typeof child === 'function') { 12 child = child.options 13 } 14 // 格式化屬性名稱 15 normalizeProps(child, vm) 16 // 格式化依賴注入內容 17 normalizeInject(child, vm) 18 // 格式化指令內容 19 normalizeDirectives(child) 20 21 // Apply extends and mixins on the child options, 22 // but only if it is a raw options object that isn't 23 // the result of another mergeOptions call. 24 // Only merged options has the _base property. 25 // 只有合併的options擁有_base屬性 26 // 須要遞歸進行合併屬性 27 // 首先合併extends和mixins 28 if (!child._base) { 29 if (child.extends) { 30 parent = mergeOptions(parent, child.extends, vm) 31 } 32 if (child.mixins) { 33 for (let i = 0, l = child.mixins.length; i < l; i++) { 34 parent = mergeOptions(parent, child.mixins[i], vm) 35 } 36 } 37 } 38 39 const options = {} 40 let key 41 for (key in parent) { 42 mergeField(key) 43 } 44 for (key in child) { 45 if (!hasOwn(parent, key)) { 46 mergeField(key) 47 } 48 } 49 // 合併屬性 50 function mergeField (key) { 51 // 獲取屬性的合併策略 52 const strat = strats[key] || defaultStrat 53 // 調用屬性合併策略,返回值爲屬性合併結果 54 options[key] = strat(parent[key], child[key], vm, key) 55 } 56 return options 57 }
具體某個字段的合併,調用的是mergeField方法,此方法主要是獲取代碼混入策略,返回值做爲混入的結果。經過調試咱們能夠看出,混入的策略對象中包含咱們常見的vue屬性,以下所示:數組
混入的實現,採用了策略模式的設計模式,在對組件數據初始化時,會遍歷組件的配置文件,根據配置文件,調用對應的策略方法。若是組件中存在不是vue指定的配置,就是策略類strats中不包含的屬性,就會調用默認的合併方法defaultStrat,該方法的定義以下:閉包
/** * Default strategy. * 默認的屬性合併策略,採用就近原則,若是子級沒有,就採用父級的 */ const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal }
strats是從哪來的呢? 屬性合併策略strats默認會讀取config中的配置文件,在項目初始化時,首先會初始化全局的api,將config文件掛載到Vue構造方法中,再者會初始化全部的屬性合併策略,以下圖所示:less
若是咱們要自定義屬性合併策略,只須要覆蓋掉Vue構造方法中的合併策略,也就是全局定義一個合併策略,以下所示:ide
// 自定義屬性合併策略 Vue.config.optionMergeStrategies.methods = function (toVal, fromVal) { if (toVal && fromVal) return fromVal if (toVal) return toVal if (fromVal) return fromVal }
vue對組件配置項的每一部分執行的合併策略有所差別,每一項的合併策略,主要分爲覆蓋、保留,以下所示:函數
data的混入返回的是一個function,參數保持對父級的引用,在初始化state的時候會調用,data的混入策略以下:ui
// 定義data的屬性合併策略 strats.data = function ( parentVal: any, childVal: any, vm?: Component ): ?Function { if (!vm) { if (childVal && typeof childVal !== 'function') { process.env.NODE_ENV !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ) return parentVal } return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) }
/** * Data * 合併對象 or 數組 * 利用閉包,返回一個包含私有參數的執行方法 */ export function mergeDataOrFn ( parentVal: any, childVal: any, vm?: Component ): ?Function { if (!vm) { // in a Vue.extend merge, both should be functions // 在Vue.extend場景中??? if (!childVal) { return parentVal } if (!parentVal) { return childVal } // 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. return function mergedDataFn () { return mergeData( typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { return function mergedInstanceDataFn () { // instance merge const instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal const defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } } } }