在上一篇文章的结尾,咱们提到在 _init
的最初阶段执行的就是 merge options
的逻辑:vue
// src/core/instance/init.js
// 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 {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
复制代码
能够看到,合并 options
分两种状况,它们的区别是什么呢。node
区别就是在执行用户编写的 new Vue(options)
时就会执行 else
逻辑,而执行内部的 new Vue(options)
(好比建立子组件实例)时就会走 if
逻辑。ios
这一节咱们就围绕下面这个例子来研究这两种状况下合并 options
分别是怎么执行的:web
import Vue from "vue";
let childComponent = {
template: "<div>{{msg}}</div>",
created() {
console.log("child created");
},
mounted() {
console.log("child mounted");
},
data() {
return {
msg: "Hello Vue"
};
}
};
Vue.mixin({
created() {
console.log("parent created");
}
});
let app = new Vue({
el: "#app",
render: h => h(childComponent)
});
复制代码
例子中使用了Vue.mixin
函数,是由于mixin
自己就是合并 options
的过程,来看 Vue.mixin
的定义:api
// src/core/global-api/mixin.js
import { mergeOptions } from "../util/index";
export function initMixin(Vue: GlobalAPI) {
Vue.mixin = function(mixin: Object) {
this.options = mergeOptions(this.options, mixin);
return this;
};
}
复制代码
能够看到 Vue.mixin
的内部实现就是调用了 mergeOptions
函数,把 mixin
中的内容合并到 Vue.options
上。数组
咱们先分析执行外部 new Vue(options)
时的状况,这时会走 else
逻辑:app
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
复制代码
这里调用了 resolveConstructorOptions
函数并传递了 vm.constructor
做为参数。resolveConstructorOptions
函数定义在 src/core/instance/init.js
文件中:编辑器
// src/core/instance/init.js
export function resolveConstructorOptions(Ctor: Class<Component>) {
let options = Ctor.options;
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super);
const cachedSuperOptions = Ctor.superOptions;
if (superOptions !== cachedSuperOptions) {
// super option changed,
// need to resolve new options.
Ctor.superOptions = superOptions;
// check if there are any late-modified/attached options (#4976)
const modifiedOptions = resolveModifiedOptions(Ctor);
// update base extend options
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions);
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions);
if (options.name) {
options.components[options.name] = Ctor;
}
}
}
return options;
}
复制代码
这里的 if
语句经过 Ctor.super
判断 Ctor
是 Vue
仍是 Vue 的子类
,显然在咱们的例子中是 Vue
,所以 if
中的逻辑不会执行。因此 resolveConstructorOptions
函数直接返回 Vue.options
。ide
那这个 Vue.options
又是从哪里来的呢,实际上它在 initGlobalAPI
函数内被定义:函数
// src/core/global-api/index.js
export function initGlobalAPI(Vue: GlobalAPI) {
// ...
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.
Vue.options._base = Vue;
extend(Vue.options.components, builtInComponents);
// ...
}
复制代码
定义 Vue.options
后遍历 ASSET_TYPES
数组往 Vue.options
添加属性,ASSET_TYPES
定义以下:
export const ASSET_TYPES = ["component", "directive", "filter"];
复制代码
以后又添加了 _base
属性。此时 Vue.options
大概是这个样子的:
Vue.options = {
components: {},
directives: {},
filters: {},
_base: function Vue(options) {}
};
复制代码
最后经过 extend(Vue.options.components, builtInComponents)
把一些内置组件扩展到 Vue.options.components
上,Vue
的内置组件目前有 <keep-alive>
、<transition>
和 <transition-group>
组件,这也就是为何咱们在其它组件中使用 <keep-alive>
组件不须要注册的缘由,这块儿后续咱们介绍 <keep-alive>
组件的时候会详细讲。
了解完 resolveConstructorOptions
后,咱们分段来分析 mergeOptions
函数:
// src/core/util/options.js
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);
// ...
}
复制代码
mergeOptions
函数的 child
参数对应的就是用户编写的 options
。这里首先调用 checkComponents(child)
来检查 options.components
组件名称是否合法:
/**
* Validate component names
*/
function checkComponents(options: Object) {
for (const key in options.components) {
validateComponentName(key);
}
}
复制代码
而后执行一系列 normalize
函数进行规范化操做。这一段代码不是本节重点,在这里不会细讲。接着看下一段:
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
): Object {
// ...
// 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);
}
}
}
// ...
}
复制代码
最外层的 if
语句代表这是对未合并的 options
的处理,由于注释提到了只有已合并的 options
才有 _base
属性。
if
中的逻辑就是递归调用 mergeOptions
函数,将 parent
分别和 child.extends
、child.mixins
合并,最后的结果赋给 parent
。
能够看到上面这两段代码都是在处理 parent
和 child
参数,mergeOptions
函数核心逻辑是接下来这一段:
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
): Object {
// ...
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;
}
复制代码
这里遍历 parent
对象的属性并调用 mergeField
函数,而后又遍历了 child
对象的属性,若是 child
对象的属性在 parent
中没有定义,一样也要调用 mergeField
函数。
mergeField
函数首先定义了 strat
, strat
实际上也是个函数,它的取值有两个来源,咱们先看这个 defaultStrat
的定义:
// src/core/util/options.js
const defaultStrat = function(parentVal: any, childVal: any): any {
return childVal === undefined ? parentVal : childVal;
};
复制代码
defaultStrat
的逻辑很简单,有 childVal
就用 childVal
,没有就用 parentVal
。咱们再来看 strats
的定义:
/**
* Option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
*/
const strats = config.optionMergeStrategies;
复制代码
这里 strats
的值是全局配置对象 config
的 optionMergeStrategies
属性,其实就是个空对象。从注释咱们能够看出来,strats
就是各类选项合并策略函数的集合,用来合并父 options
和子 options
。
咱们先来分析一下生命周期函数的合并策略:
// src/shared/constants.js
export const LIFECYCLE_HOOKS = [
"beforeCreate",
"created",
"beforeMount",
"mounted",
"beforeUpdate",
"updated",
"beforeDestroy",
"destroyed",
"activated",
"deactivated",
"errorCaptured",
"serverPrefetch"
];
复制代码
// src/core/util/options.js
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;
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook;
});
复制代码
能够看到,LIFECYCLE_HOOKS
定义了全部生命周期函数名,这些都会做为 strats
的属性名,全部属性对应的属性值都是 mergeHook
这个函数。
在mergeHook
的最后对res
还调用dedupeHooks
进行了处理,来看下dedupeHooks
函数:
// src/core/util/options.js
function dedupeHooks(hooks) {
const res = [];
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i]);
}
}
return res;
}
复制代码
其实就是数组去重处理,也就是将res
中相同的钩子函数去掉。
到这里也就印证了咱们上面的猜想:strats
就是各类选项合并策略函数的集合。回到 mergeOptions
函数:
export function mergeOptions(
parent: Object,
child: Object,
vm?: Component
): Object {
// ...
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
函数的下一步操做是将 parent
和 child
中的 key
合并到 options
中,值是调用对应的合并策略返回的结果。合并完成后 mergeOptions
函数将 options
返回出去。
这样咱们就把合并 options
的 else
逻辑走了一遍。回顾咱们在本节中举的例子,在通过合并操做后大概是这样子的:
vm.$options = {
components: {},
created: [
function created() {
console.log("parent created");
}
],
directives: {},
filters: {},
_base: function Vue(options) {
// ...
},
el: "#app",
render: function(h) {
//...
}
};
复制代码
而咱们例子中的组件 childComponent
的 options
合并处理走的是 if
逻辑,接下来咱们就来分析这种状况。
来看 if 逻辑的代码:
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
initInternalComponent(vm, options);
复制代码
if
逻辑直接调用了 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;
opts._parentVnode = parentVnode;
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;
}
}
复制代码
函数首先定义了 vm.$options = Object.create(vm.constructor.options)
,这里的 vm.constructor.options
也就是子构造函数的 options
属性,它是何时定义的呢。
回顾Vue源码探秘(九)(createComponent),子构造函数是经过 Vue.extend
建立的:
// src/core/global-api/extend.js
Vue.extend = function(extendOptions: Object): Function {
// ...
const Sub = function VueComponent(options) {
this._init(options);
};
Sub.options = mergeOptions(Super.options, extendOptions);
// ...
};
复制代码
能够看到,Sub.options
就是由 Vue.options
和 组件 options
经过 mergeOptions
合并的结果。
接着又把实例化子组件传入的子组件父 VNode
实例 parentVnode
、子组件的父 Vue
实例 parent
保存到 vm.$options
中,另外还保留了 parentVnode
配置中的 propsData
、listeners
等属性。
因此initInternalComponent
函数的逻辑其实很简单,就是作了一层对象赋值而已。对应咱们的例子,在执行了这个 if
逻辑后大概是这样子的:
vm.$options = {
parent: app,
_parentVnode: VNode,
propsData: undefined,
_componentTag: undefined,
_renderChildren: undefined,
__proto__: {
components: {},
directives: {},
filters: {},
_base: function Vue(options) {},
_Ctor: {},
created: [
function created() {
console.log("parent created");
},
function created() {
console.log("child created");
}
],
mounted: [
function mounted() {
console.log("child mounted");
}
],
data() {
return {
msg: "Hello Vue"
};
},
template: "<div>{{msg}}</div>"
}
};
复制代码
那么到这里,Vue
初始化阶段对于 options
的合并过程就介绍完了,options
的合并有两种方式:
new Vue
时,会调用
mergeOptions
函数,并根据不一样的选项调用不一样的合并策略函数
initInternalComponent
函数进行合并
下一节咱们一块儿来看下生命周期
部分的源码实现。