vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
mergeOptions
通过前面的学习我们了解到,Vue对于非组件和组件有着不同的合并options
的策略,具体体现在_init
中:
//对options进行合并
if (options && options._isComponent) {
//对组件option的合并
// 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 {
//对普通的vm的options的合并
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {
},
vm
);
}
对于组件,Vue使用了initInternalComponent
进行合并配置。对于非组件,Vue使用了resolveConstructorOptions
进行了一层处理后用mergeOptions
进行配置合并。先忽略resolveConstructorOptions
的逻辑,我们就把他的返回值当做是Vue默认的options
,定义在global-api/index.js中
:
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
好,我们继续看非组件的合并配置逻辑mergeOptions
:
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
): Object {
if (process.env.NODE_ENV !== "production") {
checkComponents(child);
}
if (typeof child === "function") {
child = child.options;
}
normalizeProps(child, vm);
normalizeInject(child, vm);
normalizeDirectives(child);
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm);
}
if (child.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;
}
一开始checkComponents
检查组件的规范性。
然后是组件的normalize
。
接下来是一个判断,使用递归将extends
和mixins
合并到parent
上。
遍历parent
,调用mergeField
。
遍历child
,如果key
不在parent
上,调用mergeField
。
mergeField
的定义,很有意思,大致逻辑是使用不同的合并策略来处理不同的key
。比如合并生命周期的策略:
function mergeHook(
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
//如果不存在childVal,就返回parentVal
//如果存在childVal且存在parentVal,就返回parentVal.concat(childVal)
//如果存在childVal且不存在parentVal,就判断childVal是不是一个数组,如果是数组就返回childVal,如果不是数组就让childVal变成输入并返回
const res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal;
return res ? dedupeHooks(res) : res;
}
大致意思是将父和子的所有生命周期钩子合并为一个数组。
回到mergeOptions
,最后返回合并完毕的options
。
接下来是组件vm
的配置合并策略。
组件的vm
是继承至Vue的,并且在继承的时候就已经merge
过一次options
了,具体代码定义在global-api/extend.js
中:
const Sub = function VueComponent(options) {
this._init(options);
};
//es5继承
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(Super.options, extendOptions);
可以看到在extend
的过程中,已经调用了一次mergeOptions
传入的是Vue
的options
和extendOptions
组件的options
,合并为Sub.options
。之后实例化组件的时候还会调用一次_init
,进入组件的合并逻辑initInternalComponent
,进入initInternalComponent
:
initInternalComponent
export function initInternalComponent(
vm: Component,
options: InternalComponentOptions
) {
const opts = (vm.$options = Object.create(vm.constructor.options));
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode;
opts.parent = options.parent; //activeInstance,当前vm的实例,也就是组件的父vm实例
opts._parentVnode = parentVnode; //占位符vnode
const vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
创建了一个空对象,__proto__
指向Sub.options
并赋值给vm.$options
,接下来就是一些赋值,就是将Sub.options
中的整理至vm.$options
中,也没有什么复杂的逻辑。
至此,Vue的配置合并逻辑解读完毕。