使用方式
-
extends的使用方式
-
允许扩展另一个组件,可以是一个简单的对象或是构造函数,而无需使用extend,其主要是为了便于扩展单文件组件,这和
mixins
类似。 -
主要功能适合组件相同的,只不过是用在单页面上,而不是全局去使用的
-
var CompA = { ... } // 在没有调用 `Vue.extend` 时候继承 CompA var CompB = { extends: CompA, }
-
-
Mixins的使用方式
-
接收个混入对象的数组,这些混入对象可以像正常的实例对象一样包含实例对象,这些选项将会被合并到最终的选项中。
-
Mixin钩子按照传入的循序依次调用,并在调用组件自身的钩子之前被调用
-
var mixin = { created: function () { console.log(1) } } var vm = new Vue({ created: function () { console.log(2) }, mixins: [mixin] }) // => 1 // => 2
-
实现原理
- 源码分析
export function mergeOptions (
parent: Object,
child: Object,
vm?: Component
): Object {
...格式化一些属性
if (!child._base) {
if (child.extends) { // 如果存在extends方法将组件进行合并 child.extends 可以是个构造函数和对象
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) { // child.mixins[i] 是个对象 ,将mixins中的数据按顺序依次进行合并
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
// 合并数据
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
这个合并函数的最主要的部分就是mergeField
函数中的strats[key]
具体的合并方式需要查看option.js
来进行查看具体属性的具体方法,下面这些数据
// strats
const strats = config.optionMergeStrategies
-
el
的合并方法-
if (process.env.NODE_ENV !== 'production') { strats.el = strats.propsData = function (parent, child, vm, key) { if (!vm) { warn( `option "${key}" can only be used during instance ` + 'creation with the `new` keyword.' ) } return defaultStrat(parent, child) } }
- 这个只是在开发环境之下进行操作,如果没有传入
vm
就提示信息,并通过defaultStrat
方法来将child
上的数据直接替换了parent
中的数据
- 这个只是在开发环境之下进行操作,如果没有传入
-
-
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) }
- 如果未传入
vm
对象:如果子元素不是一个函数,在开发环境中就将提示这个data属性应该是一个函数....
,并返回父元素;或则是将父子元素进行合并 - 如果传入
vm
对象:将父子元素进行合并
- 如果未传入
-
//mergeDataOrFn 合并方法 export function mergeDataOrFn ( parentVal: any, childVal: any, vm?: Component ): ?Function { if (!vm) { if (!childVal) { return parentVal } if (!parentVal) { return childVal } 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 } } } }
- 没有
vm
对象:父子元素哪个参数是空的就将返回另一个元素;或者将父子元素的方法指向完成之后对象进行合并返回 - 有传入
vm
对象的时候:将父子元素的函数执行之后的结果合并返回
- 没有
-
// mergeData 具体的对象合并方法 function mergeData (to: Object, from: ?Object): Object { if (!from) return to let key, toVal, fromVal 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) // 将to中不存在的from设置到to对象中 } else if ( toVal !== fromVal && // 如果两个数据不相同且都是对象的话,然后将元素中的所有进行递归的绑定 isPlainObject(toVal) && isPlainObject(fromVal) ) { mergeData(toVal, fromVal) } } return to }
- 获取到
from
中对象的所有属性,并对该属性进行循环操作。如果to
对象中不存在该属性,就将直接绑定到该to
对象中;如果to
对象中 已存在默认是不绑定该属性,如果两对象的属性值是不一样并且都是对象的时候,就将对该对象属性进行循环合并。
- 获取到
-
-
生命周期的合并方法
-
export const LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed', 'activated', 'deactivated', 'errorCaptured', 'serverPrefetch' ] LIFECYCLE_HOOKS.forEach(hook => { strats[hook] = mergeHook })
-
// mergeHook 合并钩子函数的方法 function mergeHook ( parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function> ): ?Array<Function> { const res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal return res ? dedupeHooks(res) : res }
- 主要是将父子函数放置到数组中返回,来实现合并
-
-
component
|directive
|filter
的合并方法-
export const ASSET_TYPES = [ 'component', 'directive', 'filter' ] 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) { process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) return extend(res, childVal) // 将子对象中的数据传入到父对象中 } else { return res } }
-
export function extend (to: Object, _from: ?Object): Object { for (const key in _from) { to[key] = _from[key] } return to }
- 如果
childVal
中有数据的话,就将parentVal
中的绑定所有childVal
中的参数,但对于相同属性的数据,childVal
将会覆盖parentVal
中的数据
- 如果
-
-
watch
的合并方法-
strats.watch = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { ...格式验证等 const ret = {} extend(ret, parentVal) for (const key in childVal) { let parent = ret[key] const child = childVal[key] if (parent && !Array.isArray(parent)) { parent = [parent] } ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child] } return ret }
childVal
中的属性会循环绑定到parentVal
中,如果parentVal
中是存在相同的属性,就将parentVal和
childVal`中对应的属性值放置进入数组中然后作为属性值
-
-
props
|methods
|inject
|computed
的合并方法-
strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { if (childVal && process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } if (!parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return ret }
- 其合并方式就是后者直接覆盖前者相同属性的数据,不相同的就直接绑定上去既可以
-
-
provide
的合并方法-
strats.provide = mergeDataOrFn
- 与
data
的合并方式一样
- 与
-
// config.optionMergeStrategies
export type Config = {
optionMergeStrategies: { [key: string]: Function };
};
export default ({
optionMergeStrategies: Object.create(null),
}: Config)
// defaultStrat 直接覆盖
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}
总结
data
、provide
:在合并的时候,如果目标对象不存在该属性将该属性直接绑定到目标对象上面; 但是如果目标对象已经存在了就不绑定,但是如果该属性对象的属性值两个都是对象的话,就需要对这个属性值对象进行递归的合并绑定操作。
生命周期
:在合并的时候,将目标对象的钩子函数的属性值替换成 存放着目标对象对应的函数和要绑定对象对应的函数 的数组
component
|directive
|filter
,props
|methods
|inject
|computed
:在合并的时候,都是将要绑定对象的属性进行循环,目标对象不存在就直接绑定,存在就直接覆盖
watch
:在合并的时候,如果目标对象和合并对象有相同的属性,就将相同属性进行合并,对于不相同的并不会进行绑定。合并的方式主要是将属性值替换成 包含目标对象对象方法和要绑定对象对应函数的 数组