《new Vue() 之后发生了什么?》new Vue 之后发生的第一件事就是对 options 的合并。配置的合并分为两种情况,一种是直接外部调用,也就是我们日常开发中,调用 new Vue({...}) 实例化 Vue 时的场景,另一个是在组件内部调用 new Vue({...}) 的场景。源码在 /src/core/instance/init.js 中。
先看看第一种,也就是外部调用的场景,也是我们在初始化的时候会执行的部分。
Vue.prototype._init = function (options?: Object) {
// ...
// 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
)
}
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
}
该段源码,其调用了 mergeOptions 函数,目的在于合并 resolveConstructorOptions(vm.constructor) 返回值与 options 的合并,先看 resolveConstructorOptions 实现的源码:
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
const superOptions = resolveConstructorOptions(Ctor.super)
// ...
}
return options
}
这是一个向上递归,不断查找父对象中的 options 过程。
先不看是否有父对象,就假设 Ctor.super 为 false,那这个方法返回的 options 就是 Ctor.options。
这个值是什么呢?Ctor 传过来的是 constructor,其实也就是 Vue,也就是说这个函数最后返回的 options 就是 Vue.options。
Vue.options 又是在哪儿定义的呢?在 /src/core/index.js 中,初始化了全局API,也就是调用了 initGlobalAPI() 这个方法,文件在 /src/core/global-api/index.js 中,看看源码就知道,其中有一段代码就是对 options 的定义:
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
// ...
}
显式创建了一个空对象,接着就是遍历 ASSET_TYPE,ASSET_TYPE 是在 /src/shared/constants.js 中定义的系统内置值(生命周期中的所有钩子函数也都是在这儿定义的)。
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
知道了 Vue.options 的来源后,再回头看 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
}
几个 normalize 规范化操作先不看,继续往下的代码,尤大大在注释上也说的很清楚了,递归调用 mergeOptions 将 extends 和 mixins 合并到 parent 上,也就是 Vue.options 上。
再往下调用 mergeField,设置 options,继续遍历 child,如果不在 parent 中的就在调用 mergeField 继续添加。
细看 mergeField 中的 strats,这个是在 /src/core/config.js 中定义的,其格式为 { [key: string]: Function }, 默认就是个空对象, mergeField 方法中 strats,存在 child 就返回 child,不存在的情况下,返回的就是 parent。
在 /src/core/util/options.js 中,针对 props / methods / computed / provide / assets / 生命周期的钩子 等定义了各种针对性的 key 值的合并策略。调用 mergeOptions 合并配置后的结果,大致如下:
vm.$options = {
components: {//...},
directives: {//...},
filter: {//...},
_base: function Vue() {//...},
el: '#app',
render: function(h) {//...},
//...
}
小结
常日开发中,我们也常会封装一些东西,然后对外提供一些参数,供用户自定义配置。Vue 合并配置的作用其实也是如此,其内置一些必要的默认值,在初始化阶段将用户传过来的自定义配置进行合并操作,精髓在于精细化的控制,各种针对性的合并策略。