new Vue的过程通常有2种场景

  1. 外部代码主动调用new Vue(options)的方式实例化一个Vue对象
  2. 内部通过new Vue(options)实例化子组件

无论哪种场景都会执行实例的_init(options)方法,首先会执行一个merge options
定义在src/core/instance/init.js中

  1. // ...
  2. // merge options
  3. if (options && options._isComponent) {
  4. // optimize internal component instantiation
  5. // since dynamic options merging is pretty slow, and none of the
  6. // internal component options needs special treatment.
  7. initInternalComponent(vm, options)
  8. } else {
  9. vm.$options = mergeOptions(
  10. resolveConstructorOptions(vm.constructor),
  11. options || {},
  12. vm
  13. )
  14. }
  15. // ...

不同场景对于options的合并逻辑是不一样的,且传入的options值也有非常大的不同

例子

  1. import Vue from 'vue'
  2. let childComp = {
  3. template: '<div>{{msg}}</div>',
  4. created() {
  5. console.log('child created')
  6. },
  7. mounted() {
  8. console.log('child mounted')
  9. },
  10. data() {
  11. return {
  12. msg: 'Hello Vue'
  13. }
  14. }
  15. }
  16. Vue.mixin({
  17. created() {
  18. console.log('parent created')
  19. }
  20. })
  21. let app = new Vue({
  22. el: '#app',
  23. render: h => h(childComp)
  24. })

外部调用场景

当执行new Vue时会执行this._init(options),然后执行以下代码去合并options

  1. vm.$options = mergeOptions(
  2. // 第一个参数的返回值和options做合并
  3. // 当前例子下返回值是vm.constructor.options,相当于Vue.options
  4. resolveConstructorOptions(vm.constructor),
  5. options || {},
  6. vm
  7. )

在initGlobalAPI(Vue)中定义了Vue.options
定义在src/core/global-api/index.js中

  1. export function initGlobalAPI (Vue: GlobalAPI) {
  2. // ...
  3. Vue.options = Object.create(null)
  4. ASSET_TYPES.forEach(type => {
  5. Vue.options[type + 's'] = Object.create(null)
  6. })
  7. // this is used to identify the "base" constructor to extend all plain-object
  8. // components with in Weex's multi-instance scenarios.
  9. Vue.options._base = Vue
  10. // 把一些内置组件扩展到Vue.options.components上
  11. // 内置组件有:<keep-alive>、<transition>、<transition-group>
  12. extend(Vue.options.components, builtInComponents)
  13. // ...
  14. }

ASSET_TYPES.forEach遍历结果后的代码

  1. Vue.options.components = {}
  2. Vue.options.directives = {}
  3. Vue.options.filters = {}

ASSET_TYPES

定义在src/shared/constants.js中

  1. export const ASSET_TYPES = [
  2. 'component',
  3. 'directive',
  4. 'filter'
  5. ]

mergeOptions

定义在src/core/util/option.js中

  1. /**
  2. * Merge two option objects into a new one.
  3. * Core utility used in both instantiation and inheritance.
  4. * 把parent和child这两个对象根据一些合并策略合并成一个新对象并返回
  5. */
  6. export function mergeOptions (
  7. parent: Object,
  8. child: Object,
  9. vm?: Component
  10. ): Object {
  11. if (process.env.NODE_ENV !== 'production') {
  12. checkComponents(child)
  13. }
  14. if (typeof child === 'function') {
  15. child = child.options
  16. }
  17. normalizeProps(child, vm)
  18. normalizeInject(child, vm)
  19. normalizeDirectives(child)
  20. // Apply extends and mixins on the child options,
  21. // but only if it is a raw options object that isn't
  22. // the result of another mergeOptions call.
  23. // Only merged options has the _base property.
  24. // 递归把extends和mixins合并到parent上,然后遍历parent,调用mergeField,然后再遍历child
  25. if (!child._base) {
  26. if (child.extends) {
  27. parent = mergeOptions(parent, child.extends, vm)
  28. }
  29. if (child.mixins) {
  30. for (let i = 0, l = child.mixins.length; i < l; i++) {
  31. parent = mergeOptions(parent, child.mixins[i], vm)
  32. }
  33. }
  34. }
  35. const options = {}
  36. let key
  37. for (key in parent) {
  38. mergeField(key)
  39. }
  40. for (key in child) {
  41. // 如果key不在parent的自身属性上
  42. if (!hasOwn(parent, key)) {
  43. mergeField(key)
  44. }
  45. }
  46. function mergeField (key) {
  47. // 对不同的key有不同的合并策略
  48. const strat = strats[key] || defaultStrat
  49. // 一旦parent和child都定义了相同的钩子函数,那么它们会把2个钩子函数合并成一个数组
  50. options[key] = strat(parent[key], child[key], vm, key)
  51. }
  52. // 返回合并后的结果
  53. return options
  54. }

对于生命周期函数,mergeField的合并策略
其它属性的合并策略定义在src/core/util/options.js中

  1. /**
  2. * Hooks and props are merged as arrays.
  3. */
  4. function mergeHook (
  5. parentVal: ?Array<Function>,
  6. childVal: ?Function | ?Array<Function>
  7. ): ?Array<Function> {
  8. const res = childVal
  9. ? parentVal
  10. ? parentVal.concat(childVal)
  11. : Array.isArray(childVal)
  12. ? childVal
  13. : [childVal]
  14. : parentVal
  15. return res
  16. ? dedupeHooks(res)
  17. : res
  18. }
  19. LIFECYCLE_HOOKS.forEach(hook => {
  20. strats[hook] = mergeHook
  21. })

LIFECYCLE_HOOKS

定义在src/shared/constants.js中

  1. export const LIFECYCLE_HOOKS = [
  2. 'beforeCreate',
  3. 'created',
  4. 'beforeMount',
  5. 'mounted',
  6. 'beforeUpdate',
  7. 'updated',
  8. 'beforeDestroy',
  9. 'destroyed',
  10. 'activated',
  11. 'deactivated',
  12. 'errorCaptured',
  13. 'serverPrefetch'
  14. ]

定义了所有的钩子函数名称,它们的合并策略都是mergeHook函数

例子结果

合并后vm.$options的值

  1. vm.$options = {
  2. components: { },
  3. created: [
  4. function created() {
  5. console.log('parent created')
  6. }
  7. ],
  8. directives: { },
  9. filters: { },
  10. _base: function Vue(options) {
  11. // ...
  12. },
  13. el: "#app",
  14. render: function (h) {
  15. //...
  16. }
  17. }

组件场景

组件的构造函数是通过Vue.extend继承自Vue的,代码定义在src/core/global-api/extend.js中
子组件的初始化过程,代码定义在src/core/vdom/create-component.js中

  1. export function createComponentInstanceForVnode (
  2. // we know it's MountedComponentVNode but flow doesn't
  3. vnode: any,
  4. // activeInstance in lifecycle state
  5. parent: any
  6. ): Component {
  7. const options: InternalComponentOptions = {
  8. _isComponent: true,
  9. _parentVnode: vnode,
  10. parent
  11. }
  12. // check inline-template render functions
  13. const inlineTemplate = vnode.data.inlineTemplate
  14. if (isDef(inlineTemplate)) {
  15. options.render = inlineTemplate.render
  16. options.staticRenderFns = inlineTemplate.staticRenderFns
  17. }
  18. // vnode.componentOptions.Ctor就是指向Vue.extend的返回值Sub
  19. return new vnode.componentOptions.Ctor(options)
  20. }

执行new vnode.componentOptions.Ctor(options),接着执行this._init(options),因options._isComponent为true,那么合并options的过程走到了initInternalComponent(vm, options)
定义在src/core/instance/init.js中

  1. export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  2. // vm.constructor就是子组件的构造函数Sub,相当于vm.$options = Object.create(Sub.options)
  3. const opts = vm.$options = Object.create(vm.constructor.options)
  4. // doing this because it's faster than dynamic enumeration.
  5. // 把实例化子组件时传入的子组件的父VNode实例parentVnode、子组件的父Vue实例parent保存到vm.$options中 保留parentVnode配置中的如propsData属性、listeners属性、children属性和tag属性
  6. const parentVnode = options._parentVnode
  7. opts.parent = options.parent
  8. opts._parentVnode = parentVnode
  9. const vnodeComponentOptions = parentVnode.componentOptions
  10. opts.propsData = vnodeComponentOptions.propsData
  11. opts._parentListeners = vnodeComponentOptions.listeners
  12. opts._renderChildren = vnodeComponentOptions.children
  13. opts._componentTag = vnodeComponentOptions.tag
  14. if (options.render) {
  15. opts.render = options.render
  16. opts.staticRenderFns = options.staticRenderFns
  17. }
  18. }

initInternalComponent只是做了简单一层对象赋值,并不涉及到递归、合并策略等复杂逻辑

例子结果

  1. vm.$options = {
  2. parent: Vue /*父Vue实例*/,
  3. propsData: undefined,
  4. _componentTag: undefined,
  5. _parentVnode: VNode /*父VNode实例*/,
  6. _renderChildren:undefined,
  7. __proto__: {
  8. components: { },
  9. directives: { },
  10. filters: { },
  11. _base: function Vue(options) {
  12. //...
  13. },
  14. _Ctor: {},
  15. created: [
  16. function created() {
  17. console.log('parent created')
  18. }, function created() {
  19. console.log('child created')
  20. }
  21. ],
  22. mounted: [
  23. function mounted() {
  24. console.log('child mounted')
  25. }
  26. ],
  27. data() {
  28. return {
  29. msg: 'Hello Vue'
  30. }
  31. },
  32. template: '<div>{{msg}}</div>'
  33. }
  34. }

子组件初始化过程通过 initInternalComponent 方式要比外部初始化 Vue 通过 mergeOptions 的过程要快,合并完的结果保留在 vm.$options 中