文章首发于集体博客 小灰灰的空间
选项合并策略剖析
在对 props
inject
directive
三个选项转换成对立格局之后,即开始合并选项,看一下选项合并的代码
// core/instance/init.js// 开始合并选项const options = {}let keyfor (key in parent) { // 首先将父实例中的选项合并到指标对象中 mergeField(key)}for (key in child) { // 遍历 child,如果 child 中的属性不存在 parent 本身上,则将属性合入 parent if (!hasOwn(parent, key)) { // mergeField(key) }}function mergeField (key) { // starts 对象上定义了各种选项的合策略 const strat = strats[key] || defaultStrat options[key] = strat(parent[key], child[key], vm, key)}
合并选项时,优先应用自定义合并策略,如果自定义选项策略不存在,则应用默认合并策略。 starts
对象中的每个 key 都对应了一种选项的合并策略
默认合并策略
默认的选项合并策略,如果子类选项存在则应用子类选项笼罩父类的选项
const defaultStrat = function (parentVal: any, childVal: any): any { return childVal === undefined ? parentVal : childVal}
el 选项合并策略
el 选项提供了一个在页面上已存在的 DOM
元素作为 Vue
实例的挂载指标。改选项只在 Vue
实例上才存在,其余的子类结构器上不容许存在 el
选项
if (process.env.NODE_ENV !== 'production') { strats.el = strats.propsData = function (parent, child, vm, key) { // 只有 Vue 实例才领有 el 选项,其余子类结构器不容许存在 el 选项 if (!vm) { warn( `option "${key}" can only be used during instance ` + 'creation with the `new` keyword.' ) } return defaultStrat(parent, child) }}
data 选项合并策略
strats.data = function ( parentVal: any, childVal: any, vm?: Component): ?Function { if (!vm) { if (childVal && typeof childVal !== 'function') { // 确保子类的 data 类型必须是一个函数而不是对象,应用一个对象返回一个 data 类型能够实现服复用,组件实例之间的数据不会相互影响 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 } // 调用 cmergeDataOrFn 合并选项 return cmergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm)}export function mergeDataOrFn ( parentVal: any, childVal: any, vm?: Component): ?Function { if (!vm) { // in a Vue.extend merge, both should be functions // 传入的选项为组件中的选项,因而不存在 vm 实例· if (!childVal) { return parentVal } if (!parentVal) { return childVal } return function mergedDataFn () { // data 选项在父类和子类同时存在时返回一个函数,在前面响应式零碎进行初始化时调用办法真正的合并选项 return mergeData( // 别离将子类实例和父类实例的 data 函数执行后的返回后果传递给 mergerData 进行合并 typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { // Vue 实例 中挂在的 data 属性,这里能够是一个对象 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) { // 将实例的选项,与 Vue 结构器中的选项进行合并 return mergeData(instanceData, defaultData) } else { return defaultData } } }}
剖析下面的代码能够发现,在 Vue
实例初始化过程中, data
选项并没有真正的合并,只是返回了一个函数,返回的函数外部返回了 mergeData
的执行后果,也就是说, data
选项的正式合并
是在 mergeData
函数中。来看看 mergeData
函数的实现
function mergeData (to: Object, from: ?Object): Object { if (!from) return to let key, toVal, fromVal // 通过 Reflect.ownKeys 能够获取到 Symbol 属性 const keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from) for (let i = 0; i < keys.length; i++) { key = keys[i] // in case the object is already observed... if (key === '__ob__') continue toVal = to[key] fromVal = from[key] if (!hasOwn(to, key)) { // 父类选项在子类中不存在,将父类选项增加到子类中并响应式化 set(to, key, fromVal) } else if ( // 父类选项在子类中曾经存在并且不相等且都是一般对象,进行递归 toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) { mergeData(toVal, fromVal) } } return to}
通过剖析 mergeData
办法发现, data
选项的合并准则就是,将父类的 选项合并到子类上,如果父类和子类的选项存在抵触(例如:对象属性都存在,数据类型不统一或者值不同),则保留子类的选项。
如果选项存在嵌套的状况,则须要递归调用进行合并。
知识点
ES6 中引入的 Symbol
类型在作为对象属性时时不可枚举中,在应用 Object.getOwnPropertyNames()
也不会返回 Symbol
对象的属性,然而能够应用 Object.getOwnPropertySymbols()
来获取 Symbol
属性
在下面的 mergeData
办法中, 应用 Reflect.ownKeys
办法能够获取包含 Symbol
对象属性之内的所有属性,它的返回值等同于 它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。
最初在开发中, 创立 Vue
实例时,提供选项中 data
能够是一个对象,而在创立组件时, data
选项须为一个函数。能够这样了解,创立组件的目标是为了实现服复用, data
作为一个函数时,在
创立多个组件实例是,组件实例之间的数据不会互相利用,因为每个组件实例的 data
数据都是组件模板中 data
的正本。
Vue 结构器内置选项合并
Vue 结构器中内置了 components
、directive
、filter
几个选项,无论是 Vue 根实例,还是组件实例都须要和这些选项进行合并。
// core/util/options.js// Vue 默认选项的合并,这些选项会合并到每一个 Vue 实例中ASSET_TYPES.forEach(function (type) { strats[type + 's'] = mergeAssets})function mergeAssets ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): Object { const res = Object.create(parentVal || null) // 创立一个️对象,使其原型指向父类的资源选项,对于内置的 组件、指令、过滤器须要通过原型的形式来进行调用 if (childVal) { // 开发环境下校验选项的合法性, component directive filter 这些选项须要是一个对象 process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) return extend(res, childVal) } else { return res }}
这些选项的合并策略也比较简单,先创立一个空对象,该控对象的原型指向父类的资源选项,而后将子类的选项赋值给整个空对象