Vue源码解读(五)---new Vue发生了什么

new Vue 发⽣了什么

从⼊⼝代码开始分析,我们先来分析 new Vue 背后发⽣了哪些事情。我们都知道, new 关键字在Javascript 语⾔中代表实例化是⼀个对象,⽽ Vue 实际上是⼀个类,类在 Javascript 中是⽤ Function来实现的,来看⼀下源码,在 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实例时候,会判断如果当前的环境不是生产环境,且你在调用Vue的时候,没有用new操作符。就会调用warn函数,抛出一个警告。告诉你Vue是一个构造函数,需要用new操作符去调用。这个warn函数不是单纯的console.warn,而是调用的console.error方法,具体实现在src\core\util\debug.js;

warn = (msg, vm) => {
    const trace = vm ? generateComponentTrace(vm) : ''

    if (config.warnHandler) {
      config.warnHandler.call(null, msg, vm, trace)
    } else if (hasConsole && (!config.silent)) {
      console.error(`[Vue warn]: ${msg}${trace}`)
    }
  }

接下来,把options作为参数调用_init方法。options不做过多的介绍了,就是你调用new Vue时候传入的参数。在深入_init方法之前,我们先把目光移到index.js文件里

function Vue (options) {
  ...
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

Vue的构造函数定义之后,有一系列方法会被立即调用。这些方法主要用来给Vue函数添加一些原型属性和方法的.

Vue原型中的_init方法就是在initMixin(Vue)方法中添加的。

可以看到 Vue 只能通过 new 关键字初始化,然后会调⽤ this._init ⽅法

Vue.prototype._init = function (options?: Object) {
      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
      // 有子组件时,options._isComponent才会为true
      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) //该方法主要就是为vm.$options添加一些属性, 后面讲到组件的时候再详细介绍
      } else {
        // 即当前Vue实例不是组件。而是实例化Vue对象时
        // 传入的options和vue自身的options进行合并
        // 可打印vm实例看到合并后的对象
        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) // 添加slot属性
      callHook(vm, 'beforeCreate') // 调用beforeCreate钩子
      initInjections(vm) // resolve injections before data/props 
      initState(vm)  // 初始化数据,进行双向绑定 state/props
      initProvide(vm) // resolve provide after data/props  注入provider的值到子组件中
      callHook(vm, 'created') // 调用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)
      }
      if (vm.$options.el) {
        vm.$mount(vm.$options.el) // 把模板转换成render函数
      }
    }

我们逐一来分析上述代码。首先缓存当前的上下文到vm变量中,方便之后调用。然后设置_uid属性。_uid属性是唯一的。当触发init方法,新建Vue实例时(当渲染组件时也会触发)uid都会递增。

initData 方法中getData将就是实现Vue实例化后,通过this.能获取到data内的数据

data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}

我们再看看 getData方法是怎样实现Vue实例化后,通过this.能获取到data内的数据

export function getData (data: Function, vm: Component): any {
  // #7573 disable dep collection when invoking data getters
  pushTarget()
  try {
    // 目的是判断是否是函数,返回一个数据对象
    return data.call(vm, vm)
  } catch (e) {
    handleError(e, vm, `data()`)
    return {}
  } finally {
    popTarget()
  }
}

Vue 初始化主要就⼲了⼏件事情,合并配置,初始化⽣命周期,初始化事件中⼼,初始化渲染,初始化 data、props、computed、watcher 等等

总结

Vue实例化的时候调用了init方法,init方法进行$options合并,并进行了生命周期,data等的相关初始化,初始化initData的过程中我们可以发现当判断玩没有变量命名冲突的时候进行一个proxy代理,最终就能通过this._data.msg的形式进行数据的访问Vue 的初始化逻辑写的⾮常清楚,把不同的功能逻辑拆成⼀些单独的函数执⾏,让主线逻辑⼀⽬了然,这样的编程思想是⾮常值得借鉴和学习的。由于我们这⼀章的⽬标是弄清楚模板和数据如何渲染成最终的 DOM,所以各种初始化逻辑我们先不看。在初始化的最后,检测到如果有 el 属性,则调⽤ vm.$mount ⽅法挂载 vm ,挂载的⽬标就是把模板渲染成最终的 DOM,那么接下来我们来分析 Vue 的挂载过程。

你可能感兴趣的:(vue)