new Vue的过程通常有2种场景
- 外部代码主动调用new Vue(options)的方式实例化一个Vue对象
- 内部通过new Vue(options)实例化子组件
无论哪种场景都会执行实例的_init(options)方法,首先会执行一个merge options
定义在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的合并逻辑是不一样的,且传入的options值也有非常大的不同
例子
import Vue from 'vue'
let childComp = {
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(childComp)
})
外部调用场景
当执行new Vue时会执行this._init(options),然后执行以下代码去合并options
vm.$options = mergeOptions(
// 第一个参数的返回值和options做合并
// 当前例子下返回值是vm.constructor.options,相当于Vue.options
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
在initGlobalAPI(Vue)中定义了Vue.options
定义在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
// 把一些内置组件扩展到Vue.options.components上
// 内置组件有:<keep-alive>、<transition>、<transition-group>
extend(Vue.options.components, builtInComponents)
// ...
}
ASSET_TYPES.forEach遍历结果后的代码
Vue.options.components = {}
Vue.options.directives = {}
Vue.options.filters = {}
ASSET_TYPES
定义在src/shared/constants.js中
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
mergeOptions
定义在src/core/util/option.js中
/**
* Merge two option objects into a new one.
* Core utility used in both instantiation and inheritance.
* 把parent和child这两个对象根据一些合并策略合并成一个新对象并返回
*/
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.
// 递归把extends和mixins合并到parent上,然后遍历parent,调用mergeField,然后再遍历child
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) {
// 如果key不在parent的自身属性上
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) {
// 对不同的key有不同的合并策略
const strat = strats[key] || defaultStrat
// 一旦parent和child都定义了相同的钩子函数,那么它们会把2个钩子函数合并成一个数组
options[key] = strat(parent[key], child[key], vm, key)
}
// 返回合并后的结果
return options
}
对于生命周期函数,mergeField的合并策略
其它属性的合并策略定义在src/core/util/options.js中
/**
* Hooks and props are merged as arrays.
*/
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
定义在src/shared/constants.js中
export const LIFECYCLE_HOOKS = [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated',
'errorCaptured',
'serverPrefetch'
]
定义了所有的钩子函数名称,它们的合并策略都是mergeHook函数
例子结果
合并后vm.$options的值
vm.$options = {
components: { },
created: [
function created() {
console.log('parent created')
}
],
directives: { },
filters: { },
_base: function Vue(options) {
// ...
},
el: "#app",
render: function (h) {
//...
}
}
组件场景
组件的构造函数是通过Vue.extend继承自Vue的,代码定义在src/core/global-api/extend.js中
子组件的初始化过程,代码定义在src/core/vdom/create-component.js中
export function createComponentInstanceForVnode (
// we know it's MountedComponentVNode but flow doesn't
vnode: any,
// activeInstance in lifecycle state
parent: any
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
// vnode.componentOptions.Ctor就是指向Vue.extend的返回值Sub
return new vnode.componentOptions.Ctor(options)
}
执行new vnode.componentOptions.Ctor(options),接着执行this._init(options),因options._isComponent为true,那么合并options的过程走到了initInternalComponent(vm, options)
定义在src/core/instance/init.js中
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
// vm.constructor就是子组件的构造函数Sub,相当于vm.$options = Object.create(Sub.options)
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
// 把实例化子组件时传入的子组件的父VNode实例parentVnode、子组件的父Vue实例parent保存到vm.$options中 保留parentVnode配置中的如propsData属性、listeners属性、children属性和tag属性
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
}
}
initInternalComponent只是做了简单一层对象赋值,并不涉及到递归、合并策略等复杂逻辑
例子结果
vm.$options = {
parent: Vue /*父Vue实例*/,
propsData: undefined,
_componentTag: undefined,
_parentVnode: VNode /*父VNode实例*/,
_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>'
}
}
子组件初始化过程通过 initInternalComponent 方式要比外部初始化 Vue 通过 mergeOptions 的过程要快,合并完的结果保留在 vm.$options 中