vue源码分析之new Vue()做了什么?

513 阅读3分钟

最近在学习Vue的源码,看了一些大神的文章,感觉还是蛮吃力的,在这里自己学习记录一下,首先说下感受,一定不要硬着头皮从源码入口去一行一行的读,不要像个无头苍蝇一样乱看,那样会非常吃力而且记忆不深, 一定要带着问题去看源码。最好是通过常见的面试题去看源码。

常见面试题之 new Vue() 初始化做了什么?

通过 new 关键字我们得知 Vue 是个构造函数,new 初始化了一个Vue的实例

Vue构造函数源码位置: vue/src/core/instance/index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

// new Vue() 会执行构造函数
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')
  }
  // 执行 _init 方法
  this._init(options)
}

// 在Vue上初始化mixin 
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

通过上述代码可以看到在Vue构造函数内部调用了_init方法

_init方法源码位置: vue/src/core/instance/init.js

// 这里的混入方法入参 Vue
export function initMixin (Vue: Class<Component>) {
  // 添加_init原型方法
  Vue.prototype._init = function (options?: Object) {
    // 修改this指向
    const vm: Component = this 
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // 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 上
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      // 初始化代理
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化生命周期
    initLifecycle(vm)
    // 初始化事件
    initEvents(vm)
    // 初始化渲染
    initRender(vm)
    // 执行beforeCreate生命周期函数
    callHook(vm, 'beforeCreate')
    // 初始化inject 注意是在初始化state之前
    initInjections(vm) // resolve injections before data/props
    // 初始化 data/props
    initState(vm)
    // 之后初始化 provide 注意是在初始化state之后
    initProvide(vm) // resolve provide after data/props
    // 执行created生命周期函数
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
    // 挂载dom
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

综上所述,得出以下结论

  1. new Vue(options) 执行构造函数(执行 _init 方法)
  2. _init方法内部修改this指向当前实例vm
  3. 合并配置项(判断_isComponent是不是true,是的话优化内部组件实例,不是的话合并配置参数,最后都是挂载到 vm.$options 上面)
  4. 初始化vm代理(initProxy方法判断当前是否支持Proxy,支持的话意思就是_renderProxy 属性搞了个代理 vm._renderProxy = new Proxy(vm, handlers), 不支持的话就是 vm._renderProxy = vm)
  5. 初始化生命周期函数、初始化事件相关、初始化渲染相关等
  6. 执行beforeCreate生命周期函数
  7. 在初始化 state/props 之前初始化注入 inject
  8. 响应式处理 data / props / methods / computed / watch 等
  9. 在初始化 state/props 之后初始化注入 provide
  10. 执行created生命周期函数
  11. 挂载dom

总结

通过阅读源码,了解到Vue初始化的大概操作流程,后面会逐渐详细和深入,循序渐进的理解和学习更深层的源码知识。如果你也想了解源码,可以关注一下。