Vue.js 源码分析一:Vue 实例化过程

232 阅读1分钟

本文分析的 Vue 源码版本是 2.6.10

Vue 官网第一个🌰就是:

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

通过new的方式创建了一个Vue的实例 app,接下来我们看一下这个Vue到底做了什么?

new Vue

src/core/instance/index.js中:

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  // 非常重要
  this._init(options)
}

我们可以看到 Vue 通过 new 关键字初始化,然后调用 this._init 方法

this._init()

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
      )
    }
  	...
    // 初始化生命周期
    initLifecycle(vm) 
    // 初始化事件中心
    initEvents(vm) 
    // 初始化渲染
    initRender(vm) 
    // 调用beforeCreate钩子函数
    callHook(vm, 'beforeCreate')
    // 初始化inject
    initInjections(vm) // resolve injections before data/props
    // 初始化状态
    initState(vm)
    // 初始化provide
    initProvide(vm) // resolve provide after data/props
    // 调用ceated钩子函数
    callHook(vm, 'created')
    ...
    // 检测到如果有 el 属性,则调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }

此段代码对应的是生命周期图示

接下来我们详细分析一下 Vue 的初始化过程

初始化过程

  1. 初始化生命周期(挂载私有属性)

    src/core/instance/lifecycle.js中:

    export function initLifecycle (vm: Component) {
      const options = vm.$options
    
      // locate first non-abstract parent
      // 在keep-alive中,会设置 abstract: true 属性,Vue 会跳过该组件实例
      let parent = options.parent
      if (parent && !options.abstract) {
        while (parent.$options.abstract && parent.$parent) {
          parent = parent.$parent
        }
        parent.$children.push(vm)
      }
      // 为组件实例挂载相应属性,并初始化
      vm.$parent = parent
      vm.$root = parent ? parent.$root : vm
    
      vm.$children = []
      vm.$refs = {}
      // watcher 实例对象
      vm._watcher = null
      // keep-alive 组件激活状态
      vm._inactive = null
      // keep-alive 组件状态属性
      vm._directInactive = false
      // 实例是否完成挂载(对应生命周期图示中的mounted)
      vm._isMounted = false
      // 实例是否完成销毁(对应生命周期图示中的destroyed)
      vm._isDestroyed = false
      // 实例是否正在销毁(介于生命周期图示中deforeDestroy和destroyed之间)
      vm._isBeingDestroyed = false
    }
    
  2. 初始化事件中心

    src/core/instance/events.js

    export function initEvents (vm: Component) {
      // 创建事件对象,存储事件
      vm._events = Object.create(null)
      // 系统事件标识位
      vm._hasHookEvent = false
      // init parent attached events
      const listeners = vm.$options._parentListeners
      if (listeners) {
        updateComponentListeners(vm, listeners)
      }
    }
    
  3. 初始化渲染(初始化$slot$createElement$attrs$listeners 等渲染属性)

    src/core/instance/render.js中:

    export function initRender (vm: Component) {
      ...
      // 处理组件slot,返回slot插槽对象
      vm.$slots = resolveSlots(options._renderChildren, renderContext)
      vm.$scopedSlots = emptyObject
      // bind the createElement fn to this instance
      // so that we get proper render context inside it.
      // args order: tag, data, children, normalizationType, alwaysNormalize
      // internal version is used by render functions compiled from templates
      vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
      // normalization is always applied for the public version, used in
      // user-written render functions.
      vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
      
      // $attrs & $listeners are exposed for easier HOC creation.
      // they need to be reactive so that HOCs using them are always updated
      const parentData = parentVnode && parentVnode.data
    
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
    	 ...
      } else {
        defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
        defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
      }
    }
    
  4. 调用了beforeCreate钩子函数

  5. 初始化 inject (状态初始化之前)

    src/core/instance/inject.js中:

    export function initInjections (vm: Component) {
      const result = resolveInject(vm.$options.inject, vm)
      if (result) {
        toggleObserving(false)
        Object.keys(result).forEach(key => {
          /* istanbul ignore else */
          if (process.env.NODE_ENV !== 'production') {
            defineReactive(vm, key, result[key], () => {
              warn(
                `Avoid mutating an injected value directly since the changes will be ` +
                `overwritten whenever the provided component re-renders. ` +
                `injection being mutated: "${key}"`,
                vm
              )
            })
          } else {
            defineReactive(vm, key, result[key])
          }
        })
        toggleObserving(true)
      }
    }
    
  6. 初始化状态

    在这里我们不详细分析 inState方法,详细分析请看《Vue.js 源码分析二:initState 原理》

    initState 方法主要是对 propsmethodsdatacomputedwathcer 等属性做了初始化操作。

  7. 初始化 provide(状态初始化之后)

    src/core/instance/inject.js中:

    export function initProvide (vm: Component) {
      const provide = vm.$options.provide
      if (provide) {
        vm._provided = typeof provide === 'function'
          ? provide.call(vm)
          : provide
      }
    }
    
  8. 调用了created钩子函数

总结

Vue 初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化状态。