金沙棋牌官方平台

当前位置:金沙棋牌 > 金沙棋牌官方平台 > vue2源码之生命周期,Vue组件初始化过程概要

vue2源码之生命周期,Vue组件初始化过程概要

来源:http://www.logblo.com 作者:金沙棋牌 时间:2019-11-29 12:45

vue2.x源码解析系列二: Vue组件初始化过程概要

2018/08/02 · JavaScript · Vue

原文出处: lihongxun945   

这里分析的是当前(2018/07/25)最新版 V2.5.16 的源码,如果你想一遍看一遍参阅源码,请务必记得切换到此版本,不然可能存在微小的差异。

图片 1

大家都知道,我们的应用是一个由Vue组件构成的一棵树,其中每一个节点都是一个 Vue 组件。我们的每一个Vue组件是如何被创建出来的,创建的过程经历了哪些步骤呢?把这些都搞清楚,那么我们对Vue的整个原理将会有很深入的理解。

从入口函数开始,有比较复杂的引用关系,为了方便大家理解,我画了一张图可以直观地看出他们之间的关系:

图片 2

vue 框架号称五分钟就能上手,半小时就能精通,这是因为其使用非常简单,就像下面一样:
let vm = new Vue({

因为最近我们组内有个分享主题,即vue2的源码学习分享,我们几个人分别分享几个不同部分,但是虽然我们的分工是每个人分享不同部分,但是源码里面并没有一个具体的分块,所以不管学习那一部分,都需要了解学习其他部分,因此想着按照每个js文件去学习是不大现实的,所以就通过一个小实例,跟着这个小实例一步一步的去源码,通过在网上看了很多的文章,整理这一篇学习笔记,即

下面我就来看看Vue的核心构造器以及其实例的属性和方法。

创建Vue实例的两步

我们创建一个Vue实例,只需要两行代码:

JavaScript

import Vue from ‘vue' new Vue(options)

1
2
import Vue from ‘vue'
new Vue(options)

而这两步分别经历了一个比较复杂的构建过程:

  1. 创建类:创建一个 Vue 构造函数,以及他的一系列原型方法和类方法
  2. 创建实例:创建一个 Vue 实例,初始化他的数据,事件,模板等

下面我们分别解析这两个阶段,其中每个阶段 又分为好多个 步骤

el: '#app',

通过一个demo实例看vue的生命周期

此次分享,旨在通过一个简单的小栗子,和大家一起学习从vm创建,到显示到页面上都经历了哪些过程。

如下栗子:

<div id="app">
  <p>{{message}}</p>
</div>
<script type="text/javascript">
  var vm = new Vue({
    el: '#app',
    data: {
      message: 'this is a vue test'
    }
  })
</script>

以上栗子会经过如下过程:

图片 3

lifecycle1.png

那么该栗子中的el和message在这些生命周期钩子中的状态如何?我们可以通过在浏览器打印出来看看,

总结为一张图就是:

图片 4

p1.png


第一阶段:创建Vue类

第一阶段是要创建一个Vue类,因为我们这里用的是原型而不是ES6中的class声明,所以拆成了三步来实现:

  1. 创建一个构造函数 Vue
  2. Vue.prototype 上创建一系列实例属性方法,比如 this.$data
  3. Vue 上创建一些全局方法,比如 Vue.use 可以注册插件

我们导入 Vue 构造函数 import Vue from ‘vue’ 的时候(new Vue(options) 之前),会生成一个Vue的构造函数,这个构造函数本身很简单,但是他上面会添加一系列的实例方法和一些全局方法,让我们跟着代码来依次看看如何一步步构造一个 Vue 类的,我们要明白每一步大致是做什么的,但是这里先不深究,因为我们会在接下来几章具体讲解每一步都做了什么,这里我们先有一个大致的概念即可。

我们看代码先从入口开始,这是我们在浏览器环境最常用的一个入口,也就是我们 import Vue 的时候直接导入的,它很简单,直接返回了 从 platforms/web/runtime/index/js 中得到的 Vue 构造函数,具体代码如下:

platforms/web/entry-runtime.js

JavaScript

import Vue from './runtime/index' export default Vue

1
2
import Vue from './runtime/index'
export default Vue

可以看到,这里不是 Vue 构造函数的定义地方,而是返回了从下面一步得到的Vue构造函数,但是做了一些平台相关的操作,比如内置 directives 注册等。这里就会有人问了,为什么不直接定义一个构造函数,而是这样不停的传递呢?因为 vue 有不同的运行环境,而每一个环境又有带不带 compiler 等不同版本,所以环境的不同以及版本的不同都会导致 Vue 类会有一些差异,那么这里会通过不同的步骤来处理这些差异,而所有的环境版本都要用到的核心代码是相同的,因此这些相同的代码就统一到 core/中了。

完整代码和我加的注释如下:

platforms/web/runtime/index.js

import Vue from 'core/index' import config from 'core/config' // 省略 import platformDirectives from './directives/index' import platformComponents from './components/index' //这里都是web平台相关的一些配置 // install platform specific utils Vue.config.mustUseProp = mustUseProp // 省略 // 注册指令和组件,这里的 directives 和 components 也是web平台上的,是内置的指令和组件,其实很少 // install platform runtime directives & components extend(Vue.options.directives, platformDirectives) // 内置的directives只有两个,`v-show` 和 `v-model` extend(Vue.options.components, platformComponents) // 内置的组件也很少,只有`keepAlive`, `transition`和 `transitionGroup` // 如果不是浏览器,就不进行 `patch` 操作了 // install platform patch function Vue.prototype.__patch__ = inBrowser ? patch : noop // 如果有 `el` 且在浏览器中,则进行 `mount` 操作 // public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) } // 省略devtool相关代码 export default Vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import Vue from 'core/index'
import config from 'core/config'
// 省略
 
import platformDirectives from './directives/index'
import platformComponents from './components/index'
 
//这里都是web平台相关的一些配置
// install platform specific utils
Vue.config.mustUseProp = mustUseProp
// 省略
 
// 注册指令和组件,这里的 directives 和 components 也是web平台上的,是内置的指令和组件,其实很少
// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives) // 内置的directives只有两个,`v-show` 和 `v-model`
extend(Vue.options.components, platformComponents) // 内置的组件也很少,只有`keepAlive`, `transition`和 `transitionGroup`
 
// 如果不是浏览器,就不进行 `patch` 操作了
// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop
 
// 如果有 `el` 且在浏览器中,则进行 `mount` 操作
// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
 
// 省略devtool相关代码
 
export default Vue

上面的代码终于把平台和配置相关的逻辑都处理完了,我们可以进入到了 core 目录,这里是Vue组件的核心代码,我们首先进入 core/index文件,发现 Vue 构造函数也不是在这里定义的。不过这里有一点值得注意的就是,这里调用了一个 initGlobalAPI 函数,这个函数是添加一些全局属性方法到 Vue 上,也就是类方法,而不是实例方法。具体他是做什么的我们后面再讲

core/index.js

import Vue from './instance/index' import { initGlobalAPI } from './global-api/index' initGlobalAPI(Vue) // 这个函数添加了一些类方法属性 // 省略一些ssr相关的内容 // 省略 Vue.version = '__VERSION__' export default Vue

1
2
3
4
5
6
7
8
9
10
11
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
 
initGlobalAPI(Vue) // 这个函数添加了一些类方法属性
 
// 省略一些ssr相关的内容
// 省略
 
Vue.version = '__VERSION__'
 
export default Vue

core/instance/index.js 这里才是真正的创建了 Vue 构造函数的地方,虽然代码也很简单,就是创建了一个构造函数,然后通过mixin把一堆实例方法添加上去。

core/instance/index.js 完整代码如下:

// 省略import语句 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) } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  省略import语句
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)
}
 
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
 
export default 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) // 构造函数有用的只有这一行代码,是不是很简单,至于这一行代码具体做了什么,在第二阶段我们详细讲解。 }

1
2
3
4
5
6
7
8
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构造函数,注意其实很简单,忽略在开发模式下的警告外,只执行了一行代码 this._init(options)。可想而知,Vue初始化必定有很多工作要做,比如数据的响应化、事件的绑定等,在第二阶段我们会详细讲解这个函数到底做了什么。这里我们暂且跳过它。

initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)

1
2
3
4
5
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

上面这五个函数其实都是在Vue.prototype上添加了一些属性方法,让我们先找一个看看具体的代码,比如initMixin 就是添加 _init 函数,没错正是我们构造函数中调用的那个 this._init(options) 哦,它里面主要是调用其他的几个初始化方法,因为比较简单,我们直接看代码:

core/instance/init.js

export function initMixin (Vue: Class<Component>) { // 就是这里,添加了一个方法 Vue.prototype._init = function (options?: Object) { // 省略,这部分我们会在第二阶段讲解 } }

1
2
3
4
5
6
export function initMixin (Vue: Class<Component>) {
  // 就是这里,添加了一个方法
  Vue.prototype._init = function (options?: Object) {
    // 省略,这部分我们会在第二阶段讲解
  }
}

另外的几个同样都是在 Vue.prototype 上添加了一些方法,这里暂时先不一个个贴代码,总结一下如下:

  1. core/instance/state.js,主要是添加了 $data,$props,$watch,$set,$delete 几个属性和方法
  2. core/instance/events.js,主要是添加了 $on,$off,$once,$emit 三个方法
  3. core/instance/lifecycle.js,主要添加了 _update, $forceUpdate, $destroy 三个方法
  4. core/instance/renderMixin.js,主要添加了 $nextTick_render 两个方法以及一大堆renderHelpers

还记得我们跳过的在core/index.js中 添加 globalAPI的代码吗,前面的代码都是在 Vue.prototype 上添加实例属性,让我们回到 core/index 文件,这一步需要在 Vue 上添加一些全局属性方法。前面讲到过,是通过 initGlobalAPI 来添加的,那么我们直接看看这个函数的样子:

export function initGlobalAPI (Vue: GlobalAPI) { // config const configDef = {} configDef.get = () => config // 省略 // 这里添加了一个`Vue.config` 对象,至于在哪里会用到,后面会讲 Object.defineProperty(Vue, 'config', configDef) // exposed util methods. // NOTE: these are not considered part of the public API - avoid relying on // them unless you are aware of the risk. Vue.util = { warn, extend, mergeOptions, defineReactive } //一般我们用实例方法而不是这三个类方法 Vue.set = set Vue.delete = del Vue.nextTick = nextTick // 注意这里,循环出来的结果其实是三个 `components`,`directives`, `filters`,这里先创建了空对象作为容器,后面如果有对应的插件就会放进来。 Vue.options = Object.create(null) ASSET_TYPES.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) // this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue // 内置组件只有一个,就是 `keepAlive` extend(Vue.options.components, builtInComponents) initUse(Vue) // 添加了 Vue.use 方法,可以注册插件 initMixin(Vue) //添加了Vue.mixin 方法 initExtend(Vue) // 添加了 Vue.extend 方法 // 这一步是注册了 `Vue.component` ,`Vue.directive` 和 `Vue.filter` 三个方法,上面不是有 `Vue.options.components` 等空对象吗,这三个方法的作用就是把注册的组件放入对应的容器中。 initAssetRegisters(Vue) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  // 省略
 
  // 这里添加了一个`Vue.config` 对象,至于在哪里会用到,后面会讲
  Object.defineProperty(Vue, 'config', configDef)
 
  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }
  
  //一般我们用实例方法而不是这三个类方法
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick
  
  // 注意这里,循环出来的结果其实是三个 `components`,`directives`, `filters`,这里先创建了空对象作为容器,后面如果有对应的插件就会放进来。
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
 
  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue
 
  // 内置组件只有一个,就是 `keepAlive`
  extend(Vue.options.components, builtInComponents)
 
  initUse(Vue) // 添加了 Vue.use 方法,可以注册插件
  initMixin(Vue) //添加了Vue.mixin 方法
  initExtend(Vue) // 添加了 Vue.extend 方法
 
  // 这一步是注册了 `Vue.component` ,`Vue.directive` 和 `Vue.filter` 三个方法,上面不是有 `Vue.options.components` 等空对象吗,这三个方法的作用就是把注册的组件放入对应的容器中。
  initAssetRegisters(Vue)
}

至此,我们就构建出了一个 Vue 类,这个类上的方法都已经添加完毕。这里再次强调一遍,这个阶段只是添加方法而不是执行他们,具体执行他们是要到第二阶段的。总结一下,我们创建的Vue类都包含了哪些内容:

//构造函数 function Vue () { this._init() } //全局config对象,我们几乎不会用到 Vue.config = { keyCodes, _lifecycleHooks: ['beforeCreate', 'created', ...] } // 默认的options配置,我们每个组件都会继承这个配置。 Vue.options = { beforeCreate, // 比如 vue-router 就会注册这个回调,因此会每一个组件继承 components, // 前面提到了,默认组件有三个 `KeepAlive`,`transition`, `transitionGroup`,这里注册的组件就是全局组件,因为任何一个组件中不用声明就能用了。所以全局组件的原理就是这么简单 directives, // 默认只有 `v-show` 和 `v-model` filters // 不推荐使用了 } //一些全局方法 Vue.use // 注册插件 Vue.component // 注册组件 Vue.directive // 注册指令 Vue.nextTick //下一个tick执行函数 Vue.set/delete // 数据的修改操作 Vue.mixin // 混入mixin用的 //Vue.prototype 上有几种不同作用的方法 //由initMixin 添加的 `_init` 方法,是Vue实例初始化的入口方法,会调用其他的功能初始话函数 Vue.prototype._init // 由 initState 添加的三个用来进行数据操作的方法 Vue.prototype.$data Vue.prototype.$props Vue.prototype.$watch // 由initEvents添加的事件方法 Vue.prototype.$on Vue.prototype.$off Vue.prototype.$one Vue.prototype.$emit // 由 lifecycle添加的生命周期相关的方法 Vue.prototype._update Vue.prototype.$forceUpdate Vue.prototype.$destroy //在 platform 中添加的生命周期方法 Vue.prototype.$mount // 由renderMixin添加的`$nextTick` 和 `_render` 以及一堆renderHelper Vue.prototype.$nextTick Vue.prototype._render Vue.prototype._b Vue.prototype._e //...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//构造函数
function Vue () {
  this._init()
}
 
//全局config对象,我们几乎不会用到
Vue.config = {
  keyCodes,
  _lifecycleHooks: ['beforeCreate', 'created', ...]
}
 
// 默认的options配置,我们每个组件都会继承这个配置。
Vue.options = {
  beforeCreate, // 比如 vue-router 就会注册这个回调,因此会每一个组件继承
  components, // 前面提到了,默认组件有三个 `KeepAlive`,`transition`, `transitionGroup`,这里注册的组件就是全局组件,因为任何一个组件中不用声明就能用了。所以全局组件的原理就是这么简单
  directives, // 默认只有 `v-show` 和 `v-model`
  filters // 不推荐使用了
}
 
//一些全局方法
Vue.use // 注册插件
Vue.component // 注册组件
Vue.directive // 注册指令
Vue.nextTick //下一个tick执行函数
Vue.set/delete // 数据的修改操作
Vue.mixin // 混入mixin用的
 
//Vue.prototype 上有几种不同作用的方法
 
//由initMixin 添加的 `_init` 方法,是Vue实例初始化的入口方法,会调用其他的功能初始话函数
Vue.prototype._init
 
// 由 initState 添加的三个用来进行数据操作的方法
Vue.prototype.$data
Vue.prototype.$props
Vue.prototype.$watch
 
// 由initEvents添加的事件方法
Vue.prototype.$on
Vue.prototype.$off
Vue.prototype.$one
Vue.prototype.$emit
 
// 由 lifecycle添加的生命周期相关的方法
Vue.prototype._update
Vue.prototype.$forceUpdate
Vue.prototype.$destroy
 
//在 platform 中添加的生命周期方法
Vue.prototype.$mount
 
// 由renderMixin添加的`$nextTick` 和 `_render` 以及一堆renderHelper
Vue.prototype.$nextTick
Vue.prototype._render
Vue.prototype._b
Vue.prototype._e
//...

上述就是我们的 Vue 类的全部了,有一些特别细小的点暂时没有列出来,如果你在后面看代码的时候,发现有哪个函数不知道在哪定义的,可以参考这里。那么让我们进入第二个阶段:创建实例阶段

data: {

源码层面

以上我们是从应用层面的生命钩子去了解了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)
}

我们看到,它首先判断了是不是通过new关键词创建,然后调用了this._init(options)。_init函数是在src/core/instance/init.js中添加的。我们先把整个函数都拿出来,然后看看每一步都做了什么。

Vue构造器

从 'src/core/index.js' 文件中可以找到Vue构造器的定义是在 'src/core/instance/index.js' 中给出的。
打开该文件,我们来看看代码。

// 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'

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)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
  • 非常简短,该构造器接收一个对象类型的 options 参数。
  • 初始化Vue实例对象时,默认调用原型对象上的 _init (Vue.prototype._init) 方法。

该文件中还通过调用一些模块,给Vue增加了一些实例方法。

  • 与数据相关的方法:$set, $delete, $watch
  • 与事件相关的方法:$on, $once, $off, $emit
  • 与生命周期相关的方法:$forceUpdate, $destroy
  • 还有一个与生命周期相关的方法 $mount 是在entiry文件(如: 'src/entries/web-runtime-with-compiler.js')中定义的。

以上的这些方法,就是对应官方API中实例方法。

第二阶段:创建 Vue 实例

我们通过 new Vue(options) 来创建一个实例,实例的创建,肯定是从构造函数开始的,然后会进行一系列的初始化操作,我们依次看一下创建过程都进行了什么初始化操作:

core/instance/index.js, 构造函数本身只进行了一个操作,就是调用 this._init(options) 进行初始化,这个在前面也提到过,这里就不贴代码了。

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) } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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)
  }
}

我们来一段一段看看上面的代码分别作了什么。

const vm: Component = this // vm 就是this的一个别名而已 // a uid vm._uid = uid++ // 唯一自增ID 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) }

1
2
3
4
5
6
7
8
9
10
11
    const vm: Component = this // vm 就是this的一个别名而已
    // a uid
    vm._uid = uid++ // 唯一自增ID
 
    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)
    }

这段代码首先生成了一个全局唯一的id。然后如果是非生产环境并且开启了 performance,那么会调用 mark 进行performance标记,这段代码就是开发模式下收集性能数据的,因为和Vue本身的运行原理无关,我们先跳过。

// a flag to avoid this being observed vm._isVue = true // merge options // // TODO 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 { // mergeOptions 本身比较简单,就是做了一个合并操作 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    //
    // TODO
    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 {
      // mergeOptions 本身比较简单,就是做了一个合并操作
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }

上面这段代码,暂时先不用管_isComponent,暂时只需要知道我们自己开发的时候使用的组件,都不是 _isComponent,所以我们会进入到 else语句中。这里主要是进行了 options的合并,最终生成了一个 $options 属性。下一章我们会详细讲解 options 合并的时候都做了什么,这里我们只需要暂时知道,他是把构造函数上的options和我们创建组件时传入的配置 options 进行了一个合并就可以了。正是由于合并了这个全局的 options 所以我们在可以直接在组件中使用全局的 directives

/* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm }

1
2
3
4
5
6
  /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }

这段代码可能看起来比较奇怪,这个 renderProxy 是干嘛的呢,其实就是定义了在 render 函数渲染模板的时候,访问属性的时候的一个代理,可以看到生产环境下就是自己。

开发环境下作了一个什么操作呢?暂时不用关心,反正知道渲染模板的时候上下文就是 vm 也就是 this 就行了。如果有兴趣可以看看非生产环境,作了一些友好的报错提醒等。

这里只需要记住,在生产环境下,模板渲染的上下文就是 vm就行了。

// expose real self vm._self = vm initLifecycle(vm) // 做了一些生命周期的初始化工作,初始化了很多变量,最主要是设置了父子组件的引用关系,也就是设置了 `$parent` 和 `$children`的值 initEvents(vm) // 注册事件,注意这里注册的不是自己的,而是父组件的。因为很明显父组件的监听器才会注册到孩子身上。 initRender(vm) // 做一些 render 的准备工作,比如处理父子继承关系等,并没有真的开始 render callHook(vm, 'beforeCreate') // 准备工作完成,接下来进入 `create` 阶段 initInjections(vm) // resolve injections before data/props initState(vm) // `data`, `props`, `computed` 等都是在这里初始化的,常见的面试考点比如`Vue是如何实现数据响应化的` 答案就在这个函数中寻找 initProvide(vm) // resolve provide after data/props callHook(vm, 'created') // 至此 `create` 阶段完成

1
2
3
4
5
6
7
8
9
10
11
  // expose real self
    vm._self = vm
 
    initLifecycle(vm) // 做了一些生命周期的初始化工作,初始化了很多变量,最主要是设置了父子组件的引用关系,也就是设置了 `$parent` 和 `$children`的值
    initEvents(vm) // 注册事件,注意这里注册的不是自己的,而是父组件的。因为很明显父组件的监听器才会注册到孩子身上。
    initRender(vm) // 做一些 render 的准备工作,比如处理父子继承关系等,并没有真的开始 render
    callHook(vm, 'beforeCreate') // 准备工作完成,接下来进入 `create` 阶段
    initInjections(vm) // resolve injections before data/props
    initState(vm) // `data`, `props`, `computed` 等都是在这里初始化的,常见的面试考点比如`Vue是如何实现数据响应化的` 答案就在这个函数中寻找
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created') // 至此 `create` 阶段完成

这一段代码承担了组件初始化的大部分工作。我直接把每一步的作用写在注释里面了。 把这几个函数都弄懂,那么我们也就差不多弄懂了Vue的整个工作原理,而我们接下来的几篇文章,其实都是从这几个函数中的某一个开始的。

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

1
2
3
4
5
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

开始mount,注意这里如果是我们的options中指定了 el 才会在这里进行 $mount,而一般情况下,我们是不设置 el 而是通过直接调用 $mount("#app") 来触发的。比如一般我们都是这样的:

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

1
2
3
4
5
6
new Vue({
  router,
  store,
  i18n,
  render: h => h(App)
}).$mount('#app')

以上就是Vue实例的初始化过程。因为在 create 阶段和 $mount 阶段都很复杂,所以后面会分几个章节来分别详细讲解。下一篇,让我们从最神秘的数据响应化说起。

1 赞 收藏 评论

图片 5

a: 1,

this._init

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    // 性能统计相关
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-init:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    //设置vm._isVue为true(监听对象变化时用于过滤vm)
    vm._isVue = true

    //_isComponent是内部创建子组件时才会添加为true的属性,我们的小栗子会直接走到了else里面。
    if (options && options._isComponent) {
      // 内部使用Vnode部分使用
      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')

    // 性能相关
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`${vm._name} init`, startTag, endTag)
    }

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

mergeOptions用于合并两个对象,不同于Object.assign的简单合并,它还对数据还进行了一系列的操作,且源码中多处用到该方法,所以后面会详细讲解这个方法。resolveConstructorOptions方法的作用是合并构造器及构造器父级上定义的options。

Vue实例对象方法

结合官方API文档和源码,具体来看看这些实例方法。

b: [1, 2, 3]

先看下resolveConstructorOptions
export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // 有super属性,说明Ctor是通过Vue.extend()方法创建的子类
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

这里的Ctor就是vm.constructor也就是Vue对象,在/src/core/global-api/index文件中,会给Vue添加了一些全局的属性或方法。

Vue.options = Object.create(null)
// Vue.options.components、Vue.options.directives、Vue.options.filters
config._assetTypes.forEach(type => {
  Vue.options[type + 's'] = Object.create(null)
})

// Vue.options._base
Vue.options._base = Vue

// Vue.options.components.KeepAlive
extend(Vue.options.components, builtInComponents)

所以,这里打印一下Ctor.options,如下所示:

Ctor.options = {
  components: {
    KeepAlive,
    Transition,
    TransitionGroup
  },
  directives: {
    model,
    show
  },
  filters: {},
  _base: Vue
}

Ctor.super是在调用Vue.extend时,才会添加的属性,这里先直接跳过。所以mergeOptions的第一个参数就是上面的Ctor.options,第二个参数是我们传入的options,第三个参数是当前对象vm。所以我们再看下mergeOptions方法:

实例方法 / 数据相关

  • #### vm.$watch

    对应方法源码 src/core/instance/state.js => Vue.prototype.$watch

    1. 该方法内部新建一个Watcher实例,并将其添加到当前Vue实例对象的 _watchers 列表中。
    2. 方法返回值是一个撤销观察函数 unwatch。如需撤销观察,只需执行 unwatch();
  • #### vm.$set

    对应方法源码 src/core/instance/state.js => Vue.prototype.$set

    1. 从代码可见,这是Global API中 Vue.set 的别名。
  • #### vm.$delete

    对应方法源码 src/core/instance/state.js => Vue.prototype.$delete

    1. 从代码可见,这是Global API中 Vue.delete 的别名。

}

ergeOptions

mergeOptions是Vue中处理属性的合并策略的地方。

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    // 如果有options.components,则判断是否组件名是否合法
    checkComponents(child)
  }
  // 格式化child的props
  normalizeProps(child)
  // 格式化child的directives
  normalizeDirectives(child)
  // options.extends
  const extendsFrom = child.extends 
  if (extendsFrom) {
    parent = typeof extendsFrom === 'function'
      ? mergeOptions(parent, extendsFrom.options, vm)
      : mergeOptions(parent, extendsFrom, vm)
  }
  // options.mixins
  if (child.mixins) { 
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      let mixin = child.mixins[i]
      if (mixin.prototype instanceof Vue) {
        mixin = mixin.options
      }
      parent = mergeOptions(parent, mixin, vm)
    }
  }
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

上面和components、props、directives、extends、mixins相关的内容我们暂且忽略

我们主要看一下data属性的合并策略,是也是Vue内置的,如下:

function mergeData (to: Object, from: ?Object): Object {
  if (!from) return to
  let key, toVal, fromVal
  const keys = Object.keys(from)
  for (let i = 0; i < keys.length; i++) {
    key = keys[i]
    toVal = to[key]
    fromVal = from[key]
    if (!hasOwn(to, key)) {
      set(to, key, fromVal)
    } else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

strats.data = function (    
parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  if (!vm) {
    if (!childVal) {
      return parentVal
    }
    if (typeof childVal !== 'function') {
      process.env.NODE_ENV !== 'production' && warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.',
        vm
      )
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    return function mergedDataFn () {
      return mergeData(
        childVal.call(this),
        parentVal.call(this)
      )
    }
  } else if (parentVal || childVal) {     // 我们的栗子会走到这里
    return function mergedInstanceDataFn () {
      // instance merge
      const instanceData = typeof childVal === 'function'
        ? childVal.call(vm)
        : childVal
      const defaultData = typeof parentVal === 'function'
        ? parentVal.call(vm)
        : undefined
      if (instanceData) {
        return mergeData(instanceData, defaultData)
      } else {
        return defaultData
      }
    }
  }
}

这里vm且data都不为空,所以会走到else if,返回的是mergedInstanceDataFn方法。关于mergedInstanceDataFn方法,我们都知道,子组件中定义data时,必须是一个函数,这里简单的判断了是函数就执行,不是就返回自身的值。然后通过mergeData去合并,其实就是递归把defaultData合并到instanceData,并观察。

最后合并之后的vm.$option如下:

vm.$option = {
  components: {
    KeepAlive,
    Transition,
    TransitionGroup
  },
  directives: {
    model,
    show
  },
  filters: {},
  _base: Vue,
  el: '#app',
  data: function mergedInstanceDataFn(){}
}

回到我们的_init接着放下看,之后如果是开发环境,则vm._renderProxy值为一个Proxy代理对象,生产环境就是vm自身,这里不展开赘述。

接着就是一系列的操作,我们一个一个来看。

实例方法 / 事件相关

  • #### vm.$on

    对应方法源码 src/core/instance/events.js => Vue.prototype.$on

    1. Vue实例对象 vm 中有 _events 属性,用来记录事件的注册信息
    2. 如果 vm 中已有 event 事件注册过,则向该事件注册列表中添加callback处理器;如果没有相关事件注册过,则先行初始化该事件注册列表,再将callback注册到列表中。
    3. 如果 event 是 hook 事件,则将 vm_hasHookEvent 属性设置为 true
    4. 执行结果返回 vm
  • #### vm.$off

    对应方法源码 src/core/instance/events.js => Vue.prototype.$off

    1. 如果没有传递参数,默认将 vm_events 清空,然后返回当前实例对象。相当于注销所有事件注册。
    2. 如果 vm 还没有注册过 event 事件,直接返回 vm
    3. 如果只提供了 event,则移除该事件所有的监听器
    4. 如果同时提供了 eventcallback,则获取该 event 的注册列表,遍历列表将给定的 callback 监听器注销。
  • #### vm.$once

    对应方法源码 src/core/instance/events.js => Vue.prototype.$once

    1. 分别调用 $on$off 来完成事件的注册和注销。
    2. 只执行一次,执行后立即移除监听器。
  • #### vm.$emit

    对应方法源码 src/core/instance/events.js => Vue.prototype.$emit

    1. 获取 vmevent 的注册列表
    2. 遍历注册列表中的所有处理器,依次调用

})

initLifecycle(vm)
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  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 = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

该方法主要就是给vm对象添加了$parent、$root、$children属性,以及一些其它的生命周期相关的标识。

options.abstract用于判断是否是抽象组件,组件的父子关系建立会跳过抽象组件,抽象组件比如keep-alive、transition等。所有的子组件$root都指向顶级组件。

实例方法 / 生命周期相关

  • #### vm.$forceUpdate

    对应方法源码 src/core/instance/lifecycle.js => Vue.prototype.$forceUpdate

    1. 如果当前实例对象存在 _watcher 属性,执行 _watcherupdate 方法。
  • #### vm.$destroy

    对应方法源码 src/core/instance/lifecycle.js => Vue.prototype.$destroy

    1. Vue实例对象 vm 如果被标记为正在销毁(_isBeingDestroyed == true),则直接返回,避免重复调用。
    2. 调用 'beforeDestroyed' 生命周期事件处理函数。
    3. _isBeingDestroyed 属性标记为 true。
    4. 清除父对象中的信息
    5. 清除watchers
    6. 清除当前对象的数据监听(observer)
    7. 调用 'destroyed' 生命周期事件处理函数。
    8. 清除所有注册的事件监听
    9. 触发视图更新
  • #### vm.$nextTick

    对应方法源码 src/core/instance/render.js => Vue.prototype.$nextTick

    1. 代码执行与 Vue.nextTick 一样,只不过方法执行的句柄(this)自动邦定到当前的Vue实例对象。
  • #### vm.$mount

    对应方法源码 src/entries/web-runtime-with-compiler.js => Vue.prototype.$mount

    1. 该方法实际上调用的是 Vue.prototype._mount 方法,定义在 'src/core/instance/lifecycle.js' 文件中。
    2. 如果实例对象 vm 中没有给定 render ,则将 'createEmptyVNode' 赋值给 'render'。
    3. 调用 'beforeMount' 生命周期事件处理函数。
    4. vm 增加watcher。
    5. 调用 'mounted' 生命周期事件处理函数。
    6. 返回 vm 实例对象。

在最开始,我传递了两个选项 el 以及 data ,很简单,官网上也是这样写的。
你肯定注意到了,我使用了 new 操作符。这就很自然的想到,Vue 就是一个构造函数,vm是 Vue构造函数 生成的实例,我们的配置项是传入构造函数的参数,是一个包括 el 属性 和 data属性的对象;

initEvents(vm)
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)
  }
}

该方法初始化事件相关的属性

Vue对象实例的属性

有关于Vue实例属性的初始化,基本上是调用 Vue.prototype._init 方法中完成的。上面提到过这个方法会在new Vue(options) 时自动调用。
这个方法定义在 'src/core/instance/init.js' 文件中,简要地看看它都做了什么。

  1. 初始化 vm.$options,主要是调用 mergeOptions 方法,将构造器的默认属性与给定的options合并后赋值为 vm.$options
  2. 初始化生命周期相关的属性,给 vm.$parent, vm.$root, vm.$children, vm.$refs 等属性赋值。
  3. 初始化 vm 事件监听,将父组件事件更新到当前对象
  4. 调用 'beforeCreate' 生命周期事件处理函数
  5. 初始化与data相关的属性,这里面有一步重要的操作,就是observe数据(vm._data,vm.$data就是对该对象的代理)。这会创建Observer对象并调用 defineReactive 函数,这是Vue实现双向数据邦定的基础。具体请参照 defineReactive 函数以及JS属性描述符相关资料。
  6. 调用 'created' 生命周期事件处理函数
  7. 初始化 render 相关的属性:$slots, $scopedSlots等。

结合官方API文档和源码,具体来看看这些实例属性的含义以及他们是如何赋初值的。

  • #### vm.$data

    Vue 实例观察的数据对象。Vue 实例代理了对其 data 对象属性的访问。

    1. 该属性不是直接定义在实例对象 vm 上,而是定义在原型对象上 => Vue.prototype.$data
    2. 该属性是只读属性,不能直接给该属性重新赋值。但可以set其内部具体的内嵌属性。
    3. 个人理解,就是应为要定义为只读属性,所以在定义在prototype对象上。
  • #### vm.$el

    Vue 实例使用的根 DOM 元素。

    1. 该属性不是在初始化时设置的,而是在调用 Vue.prototype._mountue.prototype._update 时才赋值的。
  • #### vm.$options

    用于当前 Vue 实例的初始化选项。需要在选项中包含自定义属性时会有用处

    1. 当初始化实例对象时,通过执行 Vue.prototype._init 方法时赋值的。构造器的默认属性与给定的options合并后赋值给它。
  • #### vm.$parent

    父实例,如果当前实例有的话。

    1. 当初始化实例对象时,通过执行 Vue.prototype._init 方法时赋值的。
  • #### vm.$root

    当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自已。

    1. 当初始化实例对象时,通过执行 Vue.prototype._init 方法时赋值的。
  • #### vm.$children

    当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

    1. 当初始化实例对象时,通过执行 Vue.prototype._init 方法时赋值的。
  • #### vm.$slots

    用来访问被 slot 分发的内容。每个具名 slot 有其相应的属性(例如:slot="foo" 中的内容将会在 vm.$slots.foo 中被找到)。default 属性包括了所有没有被包含在具名 slot 中的节点。
    在使用 render 函数书写一个组件时,访问 vm.$slots 最有帮助。

    1. 当初始化实例对象时,通过执行 Vue.prototype._init 方法时赋值的。
  • #### vm.$scopedSlots

    用来访问被 scoped slots。包括 default 在内的每个 slot,对象内都包含一个返回 VNodes 的函数。
    在使用 render 函数书写一个组件时,访问 vm.$slots 最有帮助。

    1. 当初始化实例对象时,通过执行 Vue.prototype._init 方法时赋值的。
  • #### vm.$refs

    一个对象,其中包含了所有拥有 ref 注册的子组件。

    1. 当初始化实例对象时,通过执行 Vue.prototype._init 方法时赋值的。
  • #### vm.$isServer

    当前 Vue 实例是否运行于服务器。

    1. 该属性不是直接定义在实例对象 vm 上,而是定义在原型对象上 => Vue.prototype.$isServer
    2. 该属性根据当前运行的环境以及 process.ven.VUE_ENV 自动设置。

那么我们下面就要受好奇心的驱动,来看看 Vue构造函数 是什么样的?

initRender(vm)
export function initRender (vm: Component) {
  vm.$vnode = null 
  vm._vnode = null 
  vm._staticTrees = null
  const parentVnode = vm.$options._parentVnode
  const renderContext = parentVnode && parentVnode.context
  vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
  vm.$scopedSlots = emptyObject

  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}

这里给vm添加了一些虚拟dom、slot等相关的属性和方法。

在 node_modulesvuesrccoreinstanceindex.js 文件里面,是下面的代码:
import { initMixin } from './init'

然后会调用beforeCreate钩子函数。

我们来看一下钩子函数的执行,callHook()方法定义在src/core/instance/lifecycle.js中,如下:

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
}

其实就是把钩子函数执行一下,其他钩子调用时也一样。

接着往下看

import { stateMixin } from './state'

initInjections(vm)和initProvide(vm)
export function initProvide (vm: Component) {
  const provide = vm.$options.provide
  if (provide) {
    vm._provided = typeof provide === 'function'
      ? provide.call(vm)
      : provide
  }
}

export function initInjections (vm: Component) {
  const inject: any = vm.$options.inject
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    // isArray here
    const isArray = Array.isArray(inject)
    const keys = isArray
      ? inject
      : hasSymbol
        ? Reflect.ownKeys(inject)
        : Object.keys(inject)

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i]
      const provideKey = isArray ? key : inject[key]
      let source = vm
      while (source) {
        if (source._provided && provideKey in source._provided) {
          if (process.env.NODE_ENV !== 'production') {
            defineReactive(vm, key, source._provided[provideKey], () => {
              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, source._provided[provideKey])
          }
          break
        }
        source = source.$parent
      }
    }
  }
}

这两个配套使用,用于将父组件_provided中定义的值,通过inject注入到子组件,且这些属性不会被观察。简单的例子如下:

<div id="app">
    <p>{{message}}</p>
    <child></child>
</div>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            message: '第一个vue实例'
        },
        components: {
            child: {
                template: "<div>{{a}}</div>",
                inject: ['a']
            }
        },
        provide: {
            a: 'a'
        }
    })
</script>

import { renderMixin } from './render'

initState(vm)
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) initWatch(vm, opts.watch)
}

这里主要就是操作数据了,props、methods、data、computed、watch,从这里开始就涉及到了Observer、Dep和Watcher,不多做讲解。

到这一步,我们看看我们的vm对象变成了什么样:

// _init
vm._uid = 0
vm._isVue = true
vm.$options = {
    components: {
        KeepAlive,
        Transition,
        TransitionGroup
    },
    directives: {
        model,
        show
    },
    filters: {},
    _base: Vue,
    el: '#app',
    data: function mergedInstanceDataFn(){}
}
vm._renderProxy = vm
vm._self = vm

// initLifecycle
vm.$parent = parent
vm.$root = parent ? parent.$root : vm

vm.$children = []
vm.$refs = {}

vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false

// initEvents   
vm._events = Object.create(null)
vm._hasHookEvent = false

// initRender
vm.$vnode = null
vm._vnode = null
vm._staticTrees = null
vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
vm.$scopedSlots = emptyObject

vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// 在 initState 中添加的属性
vm._watchers = []
vm._data
vm.message

可以打印一下此时的vm

import { eventsMixin } from './events'

然后,就会调用我们的created钩子函数。

我们看到create阶段,基本就是对传入数据的格式化、数据的双向绑定、以及一些属性的初始化。

import { lifecycleMixin } from './lifecycle'

$mount

打开src/platforms/web/web-runtime-with-compiler.js。

const idToTemplate = cached(id => {
  const el = query(id)
  return el && el.innerHTML
})

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {

      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  return mount.call(this, el, hydrating)
}

function getOuterHTML (el: Element): string {
  if (el.outerHTML) {
    return el.outerHTML
  } else {
    const container = document.createElement('div')
    container.appendChild(el.cloneNode(true))
    return container.innerHTML
  }
}

首先,通过mount = Vue.prototype.$mount保存之前定义的$mount方法,然后重写。

这里的query可以理解为document.querySelector,只不过内部判断了一下el是不是字符串,不是的话就直接返回,所以我们的el也可以直接传入dom元素。

之后判断是否有render函数,如果有就不做处理直接执行mount.call(this, el, hydrating)。如果没有render函数,则获取template,template可以是#id、模板字符串、dom元素,如果没有template,则获取el以及其子内容作为模板。

compileToFunctions是对我们最后生成的模板进行解析,生成render。这里的内容也比较多,简单说一下:

该方法创建的地方在src/compiler/index.js的createCompiler中。

function baseCompile (
  template: string,
  options: CompilerOptions
): CompiledResult {
  const ast = parse(template.trim(), options)
  optimize(ast, options)
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}


export function createCompiler (baseOptions: CompilerOptions) {
  const functionCompileCache: {
    [key: string]: CompiledFunctionResult;
  } = Object.create(null)

  function compile (
    template: string,
    options?: CompilerOptions
  ): CompiledResult {
    ...
    const compiled = baseCompile(template, finalOptions)
    ...
    return compiled
  }

  function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    options = options || {}
    ...
    // compile
    const compiled = compile(template, options)
    ...
    return (functionCompileCache[key] = res)
  }

  return {
    compile,
    compileToFunctions
  }
}

compileToFunctions中调用了compile,compile中调用了baseCompile。主要的操作就是baseCompile中的三步。

第一步,const ast = parse(template.trim(), options)。这里是解析template,生成ast。我们的例子生成的ast如下:

{
  type: 1,
  tag: 'div',
  plain: false,
  parent: undefined,
  attrs: [{name:'id', value: '"app"'}],
  attrsList: [{name:'id', value: 'app'}],
  attrsMap: {id: 'app'},
  children: [{
    type: 1,
    tag: 'p',
    plain: true,
    parent: ast,
    attrs: [],
    attrsList: [],
    attrsMap: {},
    children: [{
      expression: "_s(message)",
      text: "{{message}}",
      type: 2
    }]
}

第二步,optimize(ast, options)主要是对ast进行优化,分析出静态不变的内容部分,增加了部分属性:

{
  type: 1,
  tag: 'div',
  plain: false,
  parent: undefined,
  attrs: [{name:'id', value: '"app"'}],
  attrsList: [{name:'id', value: 'app'}],
  attrsMap: {id: 'app'},
  static: false,
  staticRoot: false,
  children: [{
    type: 1,
    tag: 'p',
    plain: true,
    parent: ast,
    attrs: [],
    attrsList: [],
    attrsMap: {},
    static: false,
    staticRoot: false,
    children: [{
      expression: "_s(message)",
      text: "{{message}}",
      type: 2,
      static: false
    }]
  }

因为我们这里只有一个动态的{{message}},所以static和staticRoot都是false。

最后一步,code = generate(ast, options),就是根据ast生成render函数和staticRenderFns数组。

最后生成的render如下:

render = function () {
    with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',[_v(_s(message))])])}
}

最后生成的staticRenderFns如下:

staticRenderFns = function () {
    with(this){return _c('p',[_v("这是"),_c('span',[_v("静态内容")])])}
}

在src/core/instance/render.js中,可以找到这里和render内返回值调用一一对应的函数。

Vue.prototype._o = markOnce
Vue.prototype._n = toNumber
Vue.prototype._s = _toString
Vue.prototype._l = renderList
Vue.prototype._t = renderSlot
Vue.prototype._q = looseEqual
Vue.prototype._i = looseIndexOf
Vue.prototype._m = renderStatic
Vue.prototype._f = resolveFilter
Vue.prototype._k = checkKeyCodes
Vue.prototype._b = bindObjectProps
Vue.prototype._v = createTextVNode
Vue.prototype._e = createEmptyVNode
Vue.prototype._u = resolveScopedSlots

从上面的内容,我们可以知道其实template最终还是转换为render函数,这也是官方文档中所说的render函数更加底层。

前面保存了mount = Vue.prototype.$mount,最后又调用了mount方法,我们来看看它干了什么。

打开src/platforms/web/web-runtime.js。

Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

这里仅仅是返回了mountComponent的执行结果,跟着代码的步伐,我们又回到了src/core/instance/lifecycle.js。

import { warn } from '../util/index'

mountComponent
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  ...
  callHook(vm, 'beforeMount')     //  调用beforeMount钩子

  let updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }

  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')     // 调用mounted钩子
  }
  return vm
}

上面的代码我简单的做了一些精简。可以看到首先调用了beforeMount钩子函数,新建了一个Watcher对象,绑定在vm._watcher上,之后就是判断如果vm.$vnode == null,则设置vm._isMounted = true并调用mounted钩子函数,最后返回vm对象。

接着简单看下Watcher,

打开src/core/observer/watcher.js

constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ) {
    this.vm = vm
    vm._watchers.push(this)
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    ...
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''

    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = function () {}
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    if (this.user) {
      try {
        value = this.getter.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      }
    } else {
      value = this.getter.call(vm, vm)
    }

    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
    return value
  }

function Vue (options) {

vm._render

updateComponent中调用了vm._render()函数,该方法在src/core/instance/render.js中。

Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const {
      render,
      staticRenderFns,
      _parentVnode
    } = vm.$options

    ...
    if (staticRenderFns && !vm._staticTrees) {
      vm._staticTrees = []
    }

    vm.$vnode = _parentVnode
    // render self
    let vnode

    vnode = render.call(vm._renderProxy, vm.$createElement)
    ...

    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
 // set parent
    vnode.parent = _parentVnode
    return vnode
  }

在该方法中,其实主要就是调用了vm.$options.render方法,我们再拿出render方法,看看它都干了什么。

render = function () {
    with(this){return _c('div',{attrs:{"id":"app"}},[_c('p',[_v(_s(message))])])}
}

函数调用过程中的this,是vm._renderProxy,是一个Proxy代理对象或vm本身。我们暂且把它当做vm本身。

_c是(a, b, c, d) => createElement(vm, a, b, c, d, false)。我们简单说一下createElement干了什么。a是要创建的标签名,这里是div。接着b是data,也就是模板解析时,添加到div上的属性等。c是子元素数组,所以这里又调用了_c来创建一个p标签。

_v是createTextVNode,也就是创建一个文本结点。_s是_toString,也就是把message转换为字符串,在这里,因为有with(this),所以message传入的就是我们data中定义的第一个vue实例。

所以,从上面可以看出,render函数返回的是一个VNode对象,也就是我们的虚拟dom对象。它的返回值,将作为vm._update的第一个参数。我们接着看该函数,返回src/core/instance/lifecycle.js

if (process.env.NODE_ENV !== 'production' &&

vm._update
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
    }
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode

    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )
    } else {
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
  }

从mountComponent中我们知道创建Watcher对象先于vm._isMounted = true。所以这里的vm._isMounted还是false,不会调用beforeUpdate钩子函数。

下面会调用vm.patch,在这一步之前,页面的dom还没有真正渲染。该方法包括真实dom的创建、虚拟dom的diff修改、dom的销毁等。

Vue.prototype.__patch定义在src/platform/web/runtime/index.js

!(this instanceof Vue)) {

updated钩子

updated钩子是在observer中执行,见src/core/observer/scheduler.js

warn('Vue is a constructor and should be called with the new keyword')

}

this._init(options)

}

initMixin(Vue)

stateMixin(Vue)

eventsMixin(Vue)

lifecycleMixin(Vue)

renderMixin(Vue)

export default V

不用害怕,我带你捋一捋,我们首先关注第8行,我摘抄出来:

function Vue (options) {

if (process.env.NODE_ENV !== 'production' && // 这个 if 判断,是当你不用new操作符来实例化Vue构造函数时,会爆出警告

!(this instanceof Vue)) {

warn('Vue is a constructor and should be called with the new keyword')

}

this._init(options) // 主要就是这一句,

}

发现了吧,Vue 的确是一个构造函数,和你平时使用的 Array, Object 等普普通通的构造函数,没有本质的区别。

在构造函数里面,我们要关心的是 this._init( options ) , 稍微我会详细的来讲,我们先看 node_modulesvuesrccoreinstanceindex.js 文件中的第16行~20行:
initMixin(Vue)

stateMixin(Vue)

eventsMixin(Vue)

lifecycleMixin(Vue)

renderMixin(Vue)

上面的代码调用了五个方法,这五个方法都是把Vue构造函数作为参数传入,其目的都是在 Vue .prototype 上挂载方法或属性,这个概念很好理解,我们在js 的原型链继承的学习中,经常把属性和方法丢到构造函数的原型上作为公有的属性和方法。

// initMixin(Vue) src/core/instance/init.js **************************************************

Vue.prototype._init = function (options?: Object) {}

// stateMixin(Vue) src/core/instance/state.js **************************************************

Vue.prototype.$data

Vue.prototype.$set = set

Vue.prototype.$delete = del

Vue.prototype.$watch = function(){}

// renderMixin(Vue) src/core/instance/render.js **************************************************

Vue.prototype.$nextTick = function (fn: Function) {}

Vue.prototype._render = function (): VNode {}

Vue.prototype._s = _toString

Vue.prototype._v = createTextVNode

Vue.prototype._n = toNumber

Vue.prototype._e = createEmptyVNode

Vue.prototype._q = looseEqual

Vue.prototype._i = looseIndexOf

Vue.prototype._m = function(){}

Vue.prototype._o = function(){}

Vue.prototype._f = function resolveFilter (id) {}

Vue.prototype._l = function(){}

Vue.prototype._t = function(){}

Vue.prototype._b = function(){}

Vue.prototype._k = function(){}

// eventsMixin(Vue) src/core/instance/events.js **************************************************

Vue.prototype.$on = function (event: string, fn: Function): Component {}

Vue.prototype.$once = function (event: string, fn: Function): Component {}

Vue.prototype.$off = function (event?: string, fn?: Function): Component {}

Vue.prototype.$emit = function (event: string): Component {}

// lifecycleMixin(Vue) src/core/instance/lifecycle.js **************************************************

Vue.prototype._mount = function(){}

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}

Vue.prototype._updateFromParent = function(){}

Vue.prototype.$forceUpdate = function () {}

Vue.prototype.$destroy = function () {}

经过上面5个方法对Vue构造函数的处理,vm实例上就可以使用这些属性和方法了。其实在其他地方,Vue 构造函数也被处理了:在src/core/index.js 文件中:
import Vue from './instance/index'

import { initGlobalAPI } from './global-api/index'

import { isServerRendering } from 'core/util/env'

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', { //为 Vue.prototype 添加$isServer属性

get: isServerRendering

})

Vue.version = 'VERSION' // 在VUE 身上挂载了 version的静态属性

export default Vue

initGlobalAPI() 的作用是在 Vue 构造函数上挂载静态属性和方法,Vue 在经过 initGlobalAPI 之后,会变成这样:

Vue.config

Vue.util = util

Vue.set = set

Vue.delete = del

Vue.nextTick = util.nextTick

Vue.options = {

components: {

KeepAlive

},

directives: {},

filters: {},

_base: Vue

}

Vue.use

Vue.mixin

Vue.cid = 0

Vue.extend

Vue.component = function(){}

Vue.directive = function(){}

Vue.filter = function(){}

Vue.prototype.$isServer

Vue.version = 'VERSION'

下一个就是 web-runtime.js 文件了,web-runtime.js 文件主要做了三件事儿:

1、覆盖 Vue.config 的属性,将其设置为平台特有的一些方法

2、Vue.options.directives 和 Vue.options.components 安装平台特有的指令和组件

3、在 Vue.prototype 上定义 patch 和 $mount

经过 web-runtime.js 文件之后,Vue 变成下面这个样子:

// 安装平台特定的utils

Vue.config.isUnknownElement = isUnknownElement

Vue.config.isReservedTag = isReservedTag

Vue.config.getTagNamespace = getTagNamespace

Vue.config.mustUseProp = mustUseProp

// 安装平台特定的 指令 和 组件

Vue.options = {

components: {

KeepAlive,

Transition,

TransitionGroup

},

directives: {

model,

show

},

filters: {},

_base: Vue

}

Vue.prototype.patch

Vue.prototype.$mount

这里要注意的是Vue.options 的变化。

最后一个处理 Vue 的文件就是入口文件 web-runtime-with-compiler.js 了,该文件做了两件事:
1、缓存来自 web-runtime.js 文件的 $mount 函数
const mount = Vue.prototype.$mount

2、在 Vue 上挂载 compile

Vue.compile = compileToFunctions

上面 compileToFunctions 函数可以将模板 template 编译为render函数。

至此,我们算是还原了 Vue 构造函数,总结一下:
1、Vue.prototype 下的属性和方法的挂载主要是在 src/core/instance 目录中的代码处理的

2、Vue 下的静态属性和方法的挂载主要是在 src/core/global-api 目录下的代码处理的

3、web-runtime.js 主要是添加web平台特有的配置、组件和指令,web-runtime-with-compiler.js 给Vue的 $mount 方法添加 compiler 编译器,支持 template。

好了,我们再回过头来看 this._init() 方法,_init() 方法就是Vue调用的第一个方法,然后将我们的参数 options 传了过去。_init() 是在 node_modulesvuesrccoreinstanceinit.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-init:${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 { // 大部分情况下是走了这个分支,也是vue第一步要做的事情,使用mergeOptions来合并参数选项

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(${vm._name} init, startTag, endTag)

}

if (vm.$options.el) {

vm.$mount(vm.$options.el)

}

}

好了,我们一开始不需要关心那么多边边角角,直接从23行代码开始看,因为大部分情况下是走了这条分支,也就是执行了下面的代码:

vm.$options = mergeOptions(

resolveConstructorOptions(vm.constructor),

options || {},

vm

)

这里是执行了 mergeOptions 函数,并将返回值赋值给 vm.$options 属性。 mergeOptions 函数接受三个参数,分别是 resolveContructorOptions方法, 我们调用 vue 构造函数传入的配置对象(如果没有就是空对象),以及 vm 实例 本身。

我们先看 resovleContructorOptions 方法, 传入的参数是 vm.constructor 。 vm.constructor 代表的是啥? const vm: Component = this 人家_init() 函数第一行就定义了,是指向_init() 函数内部的this, _init( ) 函数是 Vue.prototype上的一个方法,所以在其身上调用的时候,this 指向本身 Vue.prototype, 那么 vm.constructor 也就是指向 Vue 构造函数.

export function resolveConstructorOptions (Ctor: Class<Component>) { //ctor 就是 VUE 构造函数

let options = Ctor.options // vue 构造函数身上的 options 属性

if (Ctor.super) { // 判断是否定义了 Vue.super ,这个是用来处理继承的,我们后续再讲

const superOptions = resolveConstructorOptions(Ctor.super)

const cachedSuperOptions = Ctor.superOptions

if (superOptions !== cachedSuperOptions) {

// super option changed,

// need to resolve new options.

Ctor.superOptions = superOptions

// check if there are any late-modified/attached options (#4976)

const modifiedOptions = resolveModifiedOptions(Ctor)

// update base extend options

if (modifiedOptions) {

extend(Ctor.extendOptions, modifiedOptions)

}

options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)

if (options.name) {

options.components[options.name] = Ctor

}

}

}

return options

}

第22行,resolveConstructorOptions 方法直接返回了 Vue.options。也就是说,传递给 mergeOptions 方法的第一个参数其实是 Vue.options。那么,实际上原来的代码就变成了下面这样:

// 这是原来的代码

vm.$options = mergeOptions(

resolveConstructorOptions(vm.constructor),

options || {},

vm

)

// 实际上传过去的参数是下面这些

vm.$options = mergeOptions(

// Vue.options

{

components: {

KeepAlive,

Transition,

TransitionGroup

},

directives: {

model,

show

},

filters: {},

_base: Vue

},

// 调用Vue构造函数时传入的参数选项 options

{

el: '#app',

data: {

a: 1,

b: [1, 2, 3]

}

},

// this

vm

)

为什么要使用 mergeOptions 方法呢? 是为了 合并策略, 对于子组件和父组件如果有相同的属性(option)时要进行合并,相关文章:

http://www.tuicool.com/articles/UbqqAfY

本文由金沙棋牌发布于金沙棋牌官方平台,转载请注明出处:vue2源码之生命周期,Vue组件初始化过程概要

关键词: