new Vue()做了些什么

75 阅读1分钟

Vue项目的入口文件中经常有下面代码:

new Vue({ render: h => h(App), }).$mount('#app')

要理解这个过程,首先要有以下知识储备:

创建一个vue实例

const vm = new Vue(options) vm.__proto__ === Vue.prototype

1、vm的构造函数是Vue(ES6:vm所属的类是Vue)

2、options是new Vue的参数,一般称之为选项或构造选项(选项式API)

options选项里面有什么?

  1. 数据: data / props 属性 / propsData / computed 被计算出来的 / methods 方法 / watch 观察
  2. DOM: el 容器或挂载点 / template是html内容 / render / renderError
  3. 生命周期钩子: beforeCreate / created / beforeMount / mounted / beforeUpdate / updated / activated / deactivated / beforeDestroy / destroyed / errorCaptured
  4. 资源: directives 指令 / filters 过滤 /component 组件
  5. 组合: parent / mixins 混入 / extends 扩展 / provide 提供 / inject 注入

el--挂载点

// 基本语法
const vm = new Vue({
  // 当是string时,为css选择器;
  el: 'css选择器(标签选择器\id选择器....等) | HTMLElement'
})

创建出vue的实例后再render并挂载:

new Vue({
  render: h => h(app)
}).mount('#app')
// 等价于(原因看后面):
new Vue({
  render: h => h(app),
  el:'#app
})
// 在vue构造函数外部,可以使用vm.$el去访问改DOM节点

另外,再体验一把new Vue的传参吧:

const app = new Vue({
  // el: '#app',
  el: document.querySelector('#app'),
  data: {
    message: 'hello world!'
  },
  // 
  template: 
    `
      <div>
        <div>{{ message }}</div>
        <button @click="changeMsg">点我改变信息</button>
      </div>
    `,
  methods: {
    changeMsg () {
      this.message = 'i was changed!'
    }
  }
})

回到Vue的构造函数,让我们来look look源码吧~(版本为2.6.14,点击查看)

定位到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)
}

可以看到调用了_init方法,可以从src/core/instance/init.js

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
  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
    )
  }
  /* 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)
  callHook(vm, 'beforeCreate')
  initInjections(vm) // resolve injections before data/props
  initState(vm)
  initProvide(vm) // resolve provide after data/props
  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)
  }

  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

从源码的最后一个if判断可以看出,通过el挂载不过是自动调用了$mount挂载方法,所以上述的那个疑问解决了;

其次,通过_init方法可以看到一系列的初始化内容:

  • initLifecycle(vm)
  • initEvents(vm)
  • initRender(vm)
  • callHook(vm, 'beforeCreate')
  • initInjections(vm)
  • initState(vm)
  • initProvide(vm)
  • callHook(vm, 'created')

从上面的命名可以看出,这一系列函数是初始化生命周期、事件、渲染、inJect、State等内容,所以可以总结一下new Vue中主要做了什么事情:

  1. 调用_init方法。
  2. 通过mergeOptions函数合并options并且赋值给Vue上的$options属性。
  3. 初始化生命周期、事件、渲染、Inject、State等内容。