new Vue的过程通常有2种场景
- 外部代码主动调用new Vue(options)的方式实例化一个Vue对象
- 内部通过new Vue(options)实例化子组件
无论哪种场景都会执行实例的_init(options)方法,首先会执行一个merge options
定义在src/core/instance/init.js中
// ...// merge optionsif (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.optionsresolveConstructorOptions(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,然后再遍历childif (!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 keyfor (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]: parentValreturn 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'tvnode: any,// activeInstance in lifecycle stateparent: any): Component {const options: InternalComponentOptions = {_isComponent: true,_parentVnode: vnode,parent}// check inline-template render functionsconst inlineTemplate = vnode.data.inlineTemplateif (isDef(inlineTemplate)) {options.render = inlineTemplate.renderoptions.staticRenderFns = inlineTemplate.staticRenderFns}// vnode.componentOptions.Ctor就是指向Vue.extend的返回值Subreturn 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._parentVnodeopts.parent = options.parentopts._parentVnode = parentVnodeconst vnodeComponentOptions = parentVnode.componentOptionsopts.propsData = vnodeComponentOptions.propsDataopts._parentListeners = vnodeComponentOptions.listenersopts._renderChildren = vnodeComponentOptions.childrenopts._componentTag = vnodeComponentOptions.tagif (options.render) {opts.render = options.renderopts.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 中
