Vue进阶—new Vue()时发生了什么


一、前言

使用的vue版本是2.6.12

在项目中我们引入了Vue:import Vue from 'vue'。那么问题是vue到底从哪里来的?从node_modules中来。在node_modules路径下存在vue文件夹,vue文件夹中存在一个package.json文件。在这个文件中存在两个配置字段,它们都是程序的主入口文件。

"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",

其中module的优先级大于main的优先级。在module不存在时,main对应的配置项就是主入口文件。可以看到 dist/vue.runtime.esm.js 才是主入口文件。

为了方便,我们还是去GitHub上下载vue的源代码到本地查看 https://github.com/vuejs/vue

下载完成后,我们在编辑器打开,它的目录结构如下:
在这里插入图片描述
其中Vue.js 的源码都在 src ⽬录下,源码的⽬录结构如下:

src 
├── compiler              # 编译相关 
├── core                  # 核⼼代码 
├── platforms             # 不同平台的⽀持 
├── server                # 服务端渲染 
├── sfc                   # .vue ⽂件解析 
├── shared                # 共享代码

二、过程解析

1、Vue构造函数

new Vue(options) 时调用的是 src/core/instance/index.js 文件中的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')
  }
  this._init(options)
}

当我们新建一个Vue实例时候,会判断如果当前的环境不是生产环境,并且如果在调用Vue的时候,没有用new操作符,就会调用warn函数,抛出一个警告,告诉你Vue是一个构造函数,需要用new操作符去调用。这个warn函数并不是单纯的console.warn。

接下来,把 options 作为参数调用 _init 方法。options 就是调用 new Vue时候传入的参数。可以看到Vue构造函数的核心代码只有一行:this._init(options)

在Vue构造函数后边,还有几句代码:

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

在Vue的构造函数定义之后,有一系列方法会被立即调用。这些方法主要用来给Vue函数添加一些原型属性和方法的。然后我们去看一下在构造函数中调用的 _init()


2、Vue.prototype._init

上边我们可以看到 Vue 只能通过 new 关键字初始化,然后会调⽤this._init⽅法, 该⽅法在 src/core/instance/init.js 中定义。

export function initMixin (Vue: Class<Component>) {
    
    
	Vue.prototype._init = function (options?: Object) {
    
    
	   // 首先缓存当前的上下文到 vm变量中,方便之后调用。
	   const vm: Component = this
	   // 然后设置 _uid属性。_uid属性是唯一的。
	   // 当触发init方法,新建 Vue实例时(当渲染组件时也会触发)uid都会递增。
	   vm._uid = uid++
	   let startTag, endTag
	   
	   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.
	     // 优化组件实例,因为动态选项合并很慢,并且也没有组件的选项需要特殊对待
	     // 优化components属性
	     initInternalComponent(vm, options)
	   } else {
    
    
	     // 传入的options和vue自身的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) // 添加slot属性
	   callHook(vm, 'beforeCreate') // 调用beforeCreate钩子
	   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函数
	   }
	 }
}

_init 方法中主要做了下边几件事情:

  • 合并配置_init 的参数 options 就是 new Vue(options) 时传入的选项,在这里通过 mergeOptions 方法把 options 合并到 vm.$options 中。
  • 调用 initLifecycle(vm)initEvents(vm)initRender(vm)initState(vm) 函数进行生命周期的初始化、事件中心的初始化、渲染的初始化、data、props、computed、watch的初始化等等。其中 initState 就是将vue实例中的data、props、computed、watch等数据项做进一步得处理,其实就是做代理以及转化成可观测对象。
  • 另外,我们可以看到在 initState(vm) 执行之前,我们执行了 beforeCreate 方法,在 initState(vm) 执行之后,我们执行了 created 方法。因此在 beforeCreate 方法中,我们无法直接引用data,method,computed,watch等在 initState(vm) 中才开始存在的属性。
  • 检测是否有el属性,有的话就调用 vm.$mount(vm.$options.el) 进行挂载渲染成真实dom。

initState方法如下:

export function initState (vm: Component) {
    
    
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    
    
    initData(vm)
  } else {
    
    
    observe(vm._data = {
    
    }, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    
    
    initWatch(vm, opts.watch)
  }
}

3、小结

  1. vue只能通过new关键字初始化
  2. 合并传入的options
  3. 初始化生命周期
  4. 初始化事件中心
  5. 初始化渲染
  6. 初始化data
  7. 初始化props
  8. 初始化computed
  9. 初始化watch
  10. 最后检测是否有el属性,有的话就把上面渲染好的代码挂载到页面上

三、参考资料

人人都能懂的Vue源码系列—02—Vue构造函数

Vue源码笔记 — 数据驱动–new Vue 发生了什么

执行new Vue时到底发生了什么(一)

new vue 实例发生了什么呢? - 博客园

前端学习交流QQ群,群内学习讨论的氛围很好,大佬云集,期待您的加入:862748629 点击加入

猜你喜欢

转载自blog.csdn.net/weixin_43974265/article/details/114181405