如果你想一遍看一遍参阅源码9159.com,下面我就来

作者: 前端  发布:2019-11-21

vue2.x源码深入深入分析连串二: Vue组件开头化进程差不离

2018/08/02 · JavaScript · Vue

原来的文章出处: lihongxun945   

此间分析的是当下(2018/07/25)最新版 V2.5.16 的源码,假使您想壹重放一次参阅源码,请必得记得切换来此版本,不然恐怕存在微小的反差。

9159.com 1

我们都精晓,大家的施用是叁个由Vue组件构成的大器晚成棵树,此中每四个节点都以三个Vue 组件。大家的每三个Vue组件是什么样被创制出来的,创制的长河经历了什么样步骤呢?把那几个都搞明白,那么大家对Vue的全体原理将会有很中肯的理解。

从入口函数早先,有相比复杂的引用关系,为了有助于我们知晓,作者画了一张图可以直观地看出她们之间的关联:

9159.com 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>

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

9159.com 3

lifecycle1.png

那就是说该栗子中的el和message在这里些生命周期钩子中的状态怎么着?我们得以经过在浏览器打字与印刷出来看看,

总括为一张图正是:

9159.com 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 // 由init伊芙nts增多的风云措施 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 = `9159.com,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 赞 收藏 评论

9159.com 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. 遍历注册列表中的全体Computer,依次调用

})

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卡塔 尔(英语:State of Qatar),则直接再次来到,制止重新调用。
    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卡塔 尔(英语:State of Qatar)自动邦定到当前的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中大家理解创设沃特cher对象先于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 构造函数字传送入的配备对象(若无就是空对象卡塔 尔(英语:State of Qatar),以致 vm 实例 本人。

我们先看 resovleContructorOptions 方法, 传入的参数是 vm.constructor 。 vm.constructor 代表的是吗? const vm: Component = this 人家_init(卡塔 尔(英语:State of Qatar)函数第大器晚成行就定义了,是指向_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

本文由9159.com发布于前端,转载请注明出处:如果你想一遍看一遍参阅源码9159.com,下面我就来

关键词: