【美高梅开户网址】x源码解析种类贰,无题目文章

vue2.x源码解析连串2: Vue组件初阶化进程差不多

2018/08/02 · JavaScript
· Vue

原稿出处:
lihongxun945   

此地分析的是眼前(2018/07/贰伍)最新版 V2.5.16
的源码,要是你想贰次看一回参阅源码,请务必记得切换来此版本,否则可能存在微小的不一样。

美高梅开户网址 1

世家都晓得,大家的选拔是贰个由Vue组件构成的1棵树,在那之中每一个节点都是一个Vue
组件。大家的每贰个Vue组件是怎么着被制造出来的,创设的历程经历了怎么样步骤呢?把这个都搞掌握,那么大家对Vue的全部原理将会有很深远的精晓。

从入口函数开始,有比较复杂的引用关系,为了便利大家掌握,作者画了一张图能够直观地看出他们中间的关系:

美高梅开户网址 2

vue
框架号称5分钟就能上手,半时辰就能明白,那是因为其采纳非凡轻易,就如上边同样:
let vm = new Vue({

因为近年来大家组内有个享受主旨,即vue二的源码学习分享,我们几人分别分享多少个不等部分,但是即便大家的分工是种种人享受不相同部分,然而源码里面并不曾八个切实的分块,所以无论学习那有个别,都亟需领会学习其余部分,因而想着根据每一个js文件去学学是相当的小现实的,所以就通过二个小实例,跟着那个小实例一步一步的去源码,通过在英特网看了广大的小说,整理那一篇学习笔记,即

## 写在前面

创造Vue实例的两步

大家创设1个Vue实例,只须要两行代码:

JavaScript

import Vue from ‘vue’ new Vue(options)

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

而那两步分别经历了3个相比较复杂的创设进程:

  1. 创立类:创立多少个 Vue 构造函数,以及她的一多元原型方法和类措施
  2. 创立实例:创立1个 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.js很感兴趣,而且平日职业的才能栈也是Vue.js,那多少个月花了些时日钻探学习了须臾间Vue.js源码,并做了总计与出口。

第一等级:制造Vue类

第二品级是要创制1个Vue类,因为我们那边用的是原型而不是ES6中的class注明,所以拆成了三步来完毕:

  1. 创设一个构造函数 Vue
  2. Vue.prototype 上创办一名目大多实例属性方法,比方 this.$data
  3. Vue 上创制一些大局方法,比如 Vue.use 能够登记插件

大家导入 Vue 构造函数 import Vue from ‘vue’ 的时候(new Vue(options)
在此以前),会生成3个Vue的构造函数,这一个构造函数自个儿很轻巧,但是他方面会增添1多元的实例方法和部分大局方法,让我们跟着代码来所有人家看看怎么着一步步构造2个Vue
类的,大家要掌握每一步差不离是做哪些的,然而那里先不追究,因为大家会在接下去几章具体解说每一步都做了怎么,这里大家先有三个大约的定义就可以。

大家看代码先从入口开首,那是我们在浏览器遭逢最常用的1个输入,也正是我们
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 有差异的运作情状,而每2个条件又有带不带 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`【美高梅开户网址】x源码解析种类贰,无题目文章。和
`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行代码具体做了何等,在第一阶段大家详细批注。
}

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) {
    // 省略,这部分我们会在第二阶段讲解
  }
}

除此以外的多少个1律都以在 Vue.prototype
上增加了部分办法,那里目前先不3个个贴代码,总括一下之类:

  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 实例

咱俩由此 new Vue(options)
来创造3个实例,实例的开创,断定是从构造函数开端的,然后会开始展览1连串的早先化操作,我们种种看一下开立进度都进展了哪些伊始化操作:

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的3个别称而已 // 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的集结,最后生成了2个 $options 属性。下1章大家会详细讲授
options
合并的时候都做了怎样,那里大家只需求一时半刻知道,他是把构造函数上的options和我们成立组件时传出的布置
options 举行了3个统一就足以了。便是出于联合了这一个全局的 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`的值 init伊夫nts(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加上了中文的疏解[

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增加了1部分大局的品质或方法。

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方法:

大概会有掌握存在过错的地点,招待提issue建议,共同学习,共同升高。

}

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值为2个Proxy代理对象,生产条件正是vm本身,那里不实行赘述。

接着正是1类别的操作,我们多个二个来看。

## Vuex

})

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都指向一流组件。

我们在应用Vue.js开垦复杂的利用时,平常会遇上八个零部件共享同1个场所,亦或是多少个零件会去立异同一个气象,在行使代码量较少的时候,大家能够组件间通讯去维护修改数据,或许是因此事件总线来拓展多少的传递以及修改。然则当使用逐步庞大未来,代码就会变得难以保证,从父组件起首通过prop传递多层嵌套的数量由于层级过深而显得非凡脆弱,而事件总线也会因为零部件的加码、代码量的增大而显得交互错综复杂,难以捋清个中的传递关系。

在最初阶,笔者传递了七个选取 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)
  }
}

该形式开端化事件有关的性质

那么为啥大家不能够将数据层与组件层抽离开来吧?把数据层放到全局产生四个纯净的Store,组件层变得更薄,专门用来拓展数量的显得及操作。全体数据的改造都急需通过全局的Store来开始展览,产生二个单向数据流,使数码变化变得“可预测”。

那就是说大家上边就要受好奇心的驱动,来看看 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等相关的习性和办法。

Vuex是三个特意为Vue.js框架设计的、用于对Vue.js应用程序举汇兑况管理的库,它借鉴了Flux、redux的着力思量,将共享的数额抽离到全局,以贰个单例存放,同时使用Vue.js的响应式机制来进展飞快的图景管理与立异。正是因为Vuex使用了Vue.js内部的“响应式机制”,所以Vuex是1个特意为Vue.js设计并与之中度吻合的框架(优点是更进一步简洁高效,缺点是只可以跟Vue.js搭配使用)。具体运用格局及API能够参考[Vuex的官网](

在 \node_modules\vue\src\core\instance\index.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)
  }
}

其实就是把钩子函数试行一下,其余钩子调用时也1律。

随着往下看

先来看一下那张Vuex的多少流程图,熟稔Vuex使用的同校应该早就颇具掌握。

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和沃特cher,不多做教师。

到这一步,大家看看我们的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

Vuex达成了3个单向数据流,在全局具备一个State存放数据,全数修改State的操作必须通过Mutation进行,Mutation的还要提供了订阅者方式供外部插件调用获取State数据的立异。全部异步接口供给走Action,常见于调用后端接口异步获取更新数据,而Action也是力不从心间接改变State的,照旧必要通过Mutation来修改State的数量。末了,依据State的成形,渲染到视图上。Vuex运维注重Vue内部数据双向绑定机制,需求new贰个Vue对象来兑现“响应式化”,所以Vuex是叁个特别为Vue.js设计的境况管理库。

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

因为大家那里唯有3个动态的{{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。

动用过Vuex的心上人一定知道,Vuex的设置十一分简练,只须求提供三个store,然后实施上面两句代码即完结的Vuex的引进。

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钩子函数,新建了三个沃特cher对象,绑定在vm._watcher上,之后就是决断假如vm.$vnode
== null,则设置vm._isMounted =
true并调用mounted钩子函数,最终回到vm对象。

继之轻易看下沃特cher,

打开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
  }

“`javascript
Vue.use(Vuex);

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

/*将store放入Vue创造时的option中*/
new Vue({
el: ‘#app’,
store
});
“`

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

那么难题来了,Vuex是如何把store注入到Vue实例中去的吗?

!(this instanceof Vue)) {

updated钩子

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

Vue.js提供了[Vue.use](

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

大家来看一下Vuex的install完毕。

}

“`javascript
/*暴光给外部的插件install方法,供Vue.use调用安装插件*/
export function install (_Vue) {
if (Vue) {
/*防止再度设置(Vue.use内部也会检查评定叁回是还是不是再度设置同一个插件)*/
if (process.env.NODE_ENV !== ‘production’) {
console.error(
‘[vuex] already installed. Vue.use(Vuex) should be called only
once.’
)
}
return
}
/*保留Vue,同时用于检测是还是不是再度设置*/
Vue = _Vue
/*将vuexInit混淆进Vue的beforeCreate(Vue2.0)或_init方法(Vue1.0)*/
applyMixin(Vue)
}
“`

this._init(options)

那段install代码做了两件业务,壹件是幸免Vuex被重新恢复设置,另1件是试行applyMixin,目标是试行vuexInit方法开头化Vuex。Vuex针对Vue一.0与二.0独家张开了不一样的管理,假如是Vue一.0,Vuex会将vuexInit方法放入Vue的_init方法中,而对于Vue二.0,则会将vuexinit混淆进Vue的beforeCreacte钩子中。来看一下vuexInit的代码。

}

“`javascript
/*Vuex的init钩子,会存入每四个Vue实例等钩子列表*/
function vuexInit () {
const options = this.$options
// store injection
if (options.store) {
/*存在store其实代表的正是Root节点,直接实行store(function时)只怕使用store(非function)*/
this.$store = typeof options.store === ‘function’
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
/*子组件间接从父组件中获得$store,这样就保险了全体组件都公用了大局的同1份store*/
this.$store = options.parent.$store
}
}
“`

initMixin(Vue)

vuexInit会尝试从options中得到store,借使当前组件是根组件(Root节点),则options中会存在store,直接获取赋值给$store就可以。假诺当前组件非根组件,则透过options中的parent获取父组件的$store引用。那样1来,全数的零件都获得到了壹如既往份内部存款和储蓄器地址的Store实例,于是大家得以在每一个零件中经过this.$store欢愉地拜会全局的Store实例了。

stateMixin(Vue)

那么,什么是Store实例?

eventsMixin(Vue)

## Store

lifecycleMixin(Vue)

我们传入到根组件到store,正是Store实例,用Vuex提供到Store方法组织。

renderMixin(Vue)

“`javascript
export default new Vuex.Store({
strict: true,
modules: {
moduleA,
moduleB
}
});
“`

export default V

咱们来看一下Store的贯彻。首先是构造函数。

无须害怕,小编带您捋一捋,大家率先关注第八行,笔者摘抄出来:

“`javascript
constructor (options = {}) {
// Auto install if it is not done yet and `window` has `Vue`.
// To allow users to avoid auto-installation in some cases,
// this code should be placed here. See #731
/*
在浏览器景况下,假使插件还未设置(!Vue即决断是不是未安装),则它会活动安装。
它同意用户在少数情形下防止自动安装。
*/
if (!Vue && typeof window !== ‘undefined’ && window.Vue) {
install(window.Vue)
}

function Vue (options) {

if (process.env.NODE_ENV !== ‘production’) {
assert(Vue, `must call Vue.use(Vuex) before creating a store
instance.`)
assert(typeof Promise !== ‘undefined’, `vuex requires a Promise
polyfill in this browser.`)
assert(this instanceof Store, `Store must be called with the new
operator.`)
}

if (process.env.NODE_ENV !== ‘production’ && // 这些 if
判别,是当您绝不new操作符来实例化Vue构造函数时,会暴露警告

const {
/*2个数组,包罗应用在 store 上的插件方法。这么些插件直接接收 store
作为唯一参数,能够监听
mutation(用于外部地数量持久化、记录或调节和测试)或许提交 mutation
(用于内部数据,举个例子 websocket 或 某个观望者)*/
plugins = [],
/*使 Vuex store 进入严厉形式,在严厉形式下,任何 mutation
管理函数以外修改 Vuex state 都会抛出错误。*/
strict = false
} = options

!(this instanceof Vue)) {

/*从option中抽取state,假如state是function则进行,最后获得1个对象*/
let {
state = {}
} = options
if (typeof state === ‘function’) {
state = state()
}

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

// store internal state
/* 用来剖断严峻形式下是不是是用mutation修改state的 */
this._committing = false
/* 存放action */
this._actions = Object.create(null)
/* 存放mutation */
this._mutations = Object.create(null)
/* 存放getter */
this._wrappedGetters = Object.create(null)
/* module收集器 */
this._modules = new ModuleCollection(options)
/* 根据namespace存放module */
this._modulesNamespaceMap = Object.create(null)
/* 存放订阅者 */
this._subscribers = []
/* 用以落到实处沃特ch的Vue实例 */
this._watcherVM = new Vue()

}

// bind commit and dispatch to self
/*将dispatch与commit调用的this绑定为store对象自己,不然在组件内部this.dispatch时的this会指向组件的vm*/
const store = this
const { dispatch, commit } = this
/* 为dispatch与commit绑定this(Store实例自个儿) */
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}

this._init(options) // 首要正是这一句,

// strict mode
/*严加形式(使 Vuex store 进入严谨情势,在从严方式下,任何 mutation
管理函数以外修改 Vuex state 都会抛出荒唐)*/
this.strict = strict

}

// init root module.
// this also recursively registers all sub-modules
// and collects all module getters inside this._wrappedGetters
/*最先化根module,那也同时递归注册了全数子modle,收罗全体module的getter到_wrappedGetters中去,this._modules.root代表根module才独有保存的Module对象*/
installModule(this, state, [], this._modules.root)

察觉了吗,Vue 的确是二个构造函数,和您日常利用的 Array, Object
等普通的构造函数,未有实质的分别。

// initialize the store vm, which is responsible for the reactivity
// (also registers _wrappedGetters as computed properties)
/*
通过vm重设store,新建Vue对象使用Vue内部的响应式达成挂号state以及computed
*/
resetStoreVM(this, state)

在构造函数里面,大家要关爱的是 this._init( options ) ,
稍微小编会详细的来讲,大家先看
\node_modules\vue\src\core\instance\index.js
文件中的第2六行~20行:
initMixin(Vue)

// apply plugins
/* 调用插件 */
plugins.forEach(plugin => plugin(this))

stateMixin(Vue)

/* devtool插件 */
if (Vue.config.devtools) {
devtoolPlugin(this)
}
}
“`

eventsMixin(Vue)

Store的组织类除此而外早先化一些里边变量以外,首要实行了installModule(初步化module)以及resetStoreVM(通过VM使store“响应式”)。

lifecycleMixin(Vue)

### installModule

renderMixin(Vue)

installModule的作用重大是用为module加上namespace名字空间(假使有)后,注册mutation、action以及getter,同时递归安装全体子module。

上边的代码调用了三个方式,那多少个方法都以把Vue构造函数作为参数字传送入,其目标都以在
Vue .prototype 上挂载方法或性质,那个概念很好精晓,大家在js
的原型链承袭的就学中,平时把质量和格局丢到构造函数的原型上作为国有的性质和办法。

“`javascript
/*初始化module*/
function installModule (store, rootState, path, module, hot) {
/* 是还是不是是根module */
const isRoot = !path.length
/* 获取module的namespace */
const namespace = store._modules.getNamespace(path)

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

// register in namespace map
/* 如果有namespace则在_modulesNamespaceMap中注册 */
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}

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

// set state
if (!isRoot && !hot) {
/* 获取父级的state */
const parentState = getNestedState(rootState, path.slice(0, -1))
/* module的name */
const moduleName = path[path.length – 1]
store.`_withCommit`(() => {
/* 将子module设置称响应式的 */
Vue.set(parentState, moduleName, module.state)
})
}

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

const local = module.context = makeLocalContext(store, namespace, path)

Vue.prototype.$data

/* 遍历注册mutation */
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
registerMutation(store, namespacedType, mutation, local)
})

Vue.prototype.$set = set

/* 遍历注册action */
module.forEachAction((action, key) => {
const namespacedType = namespace + key
registerAction(store, namespacedType, action, local)
})

Vue.prototype.$delete = del

/* 遍历注册getter */
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
registerGetter(store, namespacedType, getter, local)
})

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

/* 递归安装mudule */
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child, hot)
})
}
“`

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

### resetStoreVM

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

在说resetStoreVM从前,先来看3个小demo。

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

“`javascript
let globalData = {
d: ‘hello world’
};
new Vue({
data () {
return {
$$state: {
globalData
}
}
}
});

Vue.prototype._s = _toString

/* modify */
setTimeout(() => {
globalData.d = ‘hi~’;
}, 1000);

Vue.prototype._v = createTextVNode

Vue.prototype.globalData = globalData;

Vue.prototype._n = toNumber

/* 任性模板中 */
<div>{{globalData.d}}</div>
“`

Vue.prototype._e = createEmptyVNode

上述代码在大局有二个globalData,它被传出1个Vue对象的data中,之后在自便Vue模板中对该变量举办展示,因为那时globalData已经在Vue的prototype上了于是一向通过this.prototype访问,也等于在模板中的{{prototype.d}}。此时,setTimeout在一s之后将globalData.d进行修改,大家发掘模板中的globalData.d产生了扭转。其实上述部分就是Vuex正视Vue大旨完毕数量的“响应式化”。

Vue.prototype._q = looseEqual

不熟习Vue.js响应式原理的同室能够因而笔者另一篇小说[响应式原理](

Vue.prototype._i = looseIndexOf

随着来看代码。

Vue.prototype._m = function(){}

“`javascript
/*
通过vm重设store,新建Vue对象使用Vue内部的响应式完结登记state以及computed
*/
function resetStoreVM (store, state, hot) {
/* 存放在此之前的vm对象 */
const oldVm = store._vm

Vue.prototype._o = function(){}

// bind store public getters
store.getters = {}
const wrappedGetters = store._wrappedGetters
const computed = {}

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

/*
通过Object.defineProperty为每2个getter方法设置get方法,比如获取this.$store.getters.test的时候获得的是store._vm.test,也就是Vue对象的computed属性
*/
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})

Vue.prototype._l = function(){}

// use a Vue instance to store the state tree
// suppress warnings just in case the user has added
// some funky global mixins
const silent = Vue.config.silent
/*
Vue.config.silent一时设置为true的目的是在new贰个Vue实例的历程中不会报出全体警告
*/
Vue.config.silent = true
/*
那里new了1个Vue对象,运用Vue内部的响应式落成登记state以及computed*/
store._vm = new Vue({
data: {
$$state: state
},
computed
})
Vue.config.silent = silent

Vue.prototype._t = function(){}

// enable strict mode for new vm
/* 使能严苛方式,保险修改store只可以通过mutation */
if (store.strict) {
enableStrictMode(store)
}

Vue.prototype._b = function(){}

if (oldVm) {
/* 解除旧vm的state的引用,以及销毁旧的Vue对象 */
if (hot) {
// dispatch changes in all subscribed watchers
// to force getter re-evaluation for hot reloading.
store._withCommit(() => {
oldVm._data.$$state = null
})
}
Vue.nextTick(() => oldVm.$destroy())
}
}
“`

Vue.prototype._k = function(){}

resetStoreVM首先会遍历wrappedGetters,使用Object.defineProperty方法为每三个getter绑定上get方法,那样大家就能够在组件里拜访this.$store.getter.test就同样访问store._vm.test。

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

“`javascript
forEachValue(wrappedGetters, (fn, key) => {
// use computed to leverage its lazy-caching mechanism
computed[key] = () => fn(store)
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
})
“`

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

事后Vuex采纳了new二个Vue对象来兑现数据的“响应式化”,运用Vue.js内部提供的数目双向绑定成效来贯彻store的数码与视图的1块儿更新。

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

“`javascript
store._vm = new Vue({
data: {
$$state: state
},
computed
})
“`

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

那时候我们走访store._vm.test也就走访了Vue实例中的属性。

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

那两步施行完之后,我们就足以经过this.$store.getter.test访问vm中的test属性了。

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

### 严峻方式

Vue.prototype._mount = function(){}

Vuex的Store构造类的option有一个strict的参数,能够操纵Vuex实践适度从紧形式,严谨形式下,全体修改state的操作必须经过mutation完毕,不然会抛出荒谬。

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

“`javascript
/* 使能严刻形式 */
function enableStrictMode (store) {
store._vm.$watch(function () { return this._data.$$state }, () =>
{
if (process.env.NODE_ENV !== ‘production’) {
/*
检测store中的_committing的值,假诺是true代表不是经过mutation的方法修改的
*/
assert(store._committing, `Do not mutate vuex store state outside
mutation handlers.`)
}
}, { deep: true, sync: true })
}
“`

Vue.prototype._updateFromParent = function(){}

率先,在严俊情势下,Vuex会利用vm的$watch方法来调查$$state,也正是Store的state,在它被修改的时候进入回调。大家开采,回调中只有一句话,用assert断言来检查评定store._committing,当store._committing为false的时候会触发断言,抛出尤其。

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

我们发掘,Store的commit方法中,施行mutation的言辞是如此的。

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

“`javascript
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
“`

经过地点五个章程对Vue构造函数的拍卖,vm实例上就足以选用那些属性和艺术了。其实在别的地点,Vue
构造函数也被管理了:在src/core/index.js 文件中:
import Vue from ‘./instance/index’

再来看看_withCommit的实现。

import { initGlobalAPI } from ‘./global-api/index’

“`javascript
_withCommit (fn) {
/*
调用withCommit修改state的值时会将store的committing值置为true,内部会有断言检查该值,在严谨方式下只同意利用mutation来修改store中的值,而不一致意直接改变store的数值
*/
const committing = this._committing
this._committing = true
fn()
this._committing = committing
}
“`

import { isServerRendering } from ‘core/util/env’

咱们开掘,通过commit(mutation)修改state数据的时候,会再调用mutation方法此前将committing置为true,接下去再经过mutation函数修改state中的数据,那时候触发$watch中的回调断言committing是不会抛出万分的(此时committing为true)。而当大家一贯更改state的数额时,触发$watch的回调执行断言,那时committing为false,则会抛出格外。那正是Vuex的严格方式的贯彻。

initGlobalAPI(Vue)

接下去我们来探视Store提供的一些API。

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

###
commit([mutation](

get: isServerRendering

“`javascript
/* 调用mutation的commit方法 */
commit (_type, _payload, _options) {
// check object-style commit
/* 校验参数 */
const {
type,
payload,
options
} = unifyObjectStyle(_type, _payload, _options)

})

const mutation = { type, payload }
/* 取出type对应的mutation的方法 */
const entry = this._mutations[type]
if (!entry) {
if (process.env.NODE_ENV !== ‘production’) {
console.error(`[vuex] unknown mutation type: ${type}`)
}
return
}
/* 实施mutation中的全数办法 */
this._withCommit(() => {
entry.forEach(function commitIterator (handler) {
handler(payload)
})
})
/* 布告全数订阅者 */
this._subscribers.forEach(sub => sub(mutation, this.state))

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

if (
process.env.NODE_ENV !== ‘production’ &&
options && options.silent
) {
console.warn(
`[vuex] mutation type: ${type}. Silent option has been removed. `

export default Vue

  • ‘Use the filter functionality in the vue-devtools’
    )
    }
    }
    “`

initGlobalAPI() 的成效是在 Vue 构造函数上挂载静态属性和格局,Vue
在通过 initGlobalAPI 之后,会成为那样:

commit方法会依照type找到并调用_mutations中的全部type对应的mutation方法,所以当未有namespace的时候,commit方法会触发全部module中的mutation方法。再实行完全体的mutation之后会实行_subscribers中的全数订阅者。大家来看一下_subscribers是什么。

Vue.config

Store给外部提供了1个subscribe方法,用以注册一个订阅函数,会push到Store实例的_subscribers中,同时再次来到多个从_subscribers中打消该订阅者的办法。

Vue.util = util

“`javascript
/* 注册一个订阅函数,再次回到撤销订阅的函数 */
subscribe (fn) {
const subs = this._subscribers
if (subs.indexOf(fn) < 0) {
subs.push(fn)
}
return () => {
const i = subs.indexOf(fn)
if (i > -1) {
subs.splice(i, 1)
}
}
}
“`

Vue.set = set

在commit甘休未来则会调用那些_subscribers中的订阅者,那几个订阅者形式提要求外部一个蹲点state变化的大概。state通过mutation退换时,能够使得补获这一个生成。

Vue.delete = del

### dispatch([action](

Vue.nextTick = util.nextTick

来看一下dispatch的贯彻。

Vue.options = {

“`javascript
/* 调用action的dispatch方法 */
dispatch (_type, _payload) {
// check object-style dispatch
const {
type,
payload
} = unifyObjectStyle(_type, _payload)

components: {

/* actions中取出type对应的ation */
const entry = this._actions[type]
if (!entry) {
if (process.env.NODE_ENV !== ‘production’) {
console.error(`[vuex] unknown action type: ${type}`)
}
return
}

KeepAlive

/* 是数组则包装Promise造成五个新的Promise,唯有2个则一向再次回到第0个
*/
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload)
}
“`

},

以及registerAction时候做的作业。

directives: {},

“`javascript
/* 遍历注册action */
function registerAction (store, type, handler, local) {
/* 取出type对应的action */
const entry = store._actions[type] || (store._actions[type] =
[])
entry.push(function wrappedActionHandler (payload, cb) {
let res = handler.call(store, {
dispatch: local.dispatch,
commit: local.commit,
getters: local.getters,
state: local.state,
rootGetters: store.getters,
rootState: store.state
}, payload, cb)
/* 决断是还是不是是Promise */
if (!isPromise(res)) {
/* 不是Promise对象的时候转化称Promise对象 */
res = Promise.resolve(res)
}
if (store._devtoolHook) {
/* 存在devtool插件的时候触发vuex的error给devtool */
return res.catch(err => {
store._devtoolHook.emit(‘vuex:error’, err)
throw err
})
} else {
return res
}
})
}
“`

filters: {},

因为registerAction的时候将push进_actions的action举行了壹层封装(wrappedActionHandler),所以大家在拓展dispatch的率先个参数中收获state、commit等办法。之后,推行结果res会被开始展览推断是还是不是是Promise,不是则会进行一层封装,将其转化成Promise对象。dispatch时则从_actions中抽取,只有1个的时候平素回到,否则用Promise.all管理再重临。

_base: Vue

### watch

}

“`javascript
/* 寓目2个getter方法 */
watch (getter, cb, options) {
if (process.env.NODE_ENV !== ‘production’) {
assert(typeof getter === ‘function’, `store.watch only accepts a
function.`)
}
return this._watcherVM.$watch(() => getter(this.state,
this.getters), cb, options)
}
“`

Vue.use

熟练Vue的情侣应该很熟练watch这一个办法。那里运用了相比美妙的安排,_watcherVM是二个Vue的实例,所以watch就能够直接利用了Vue内部的watch天性提供了壹种注重数据getter变动的措施。

Vue.mixin

### registerModule

Vue.cid = 0

“`javascript
/*
注册三个动态module,当职业打开异步加载的时候,能够因而该接口实行挂号动态module
*/
registerModule (path, rawModule) {
/* 转化称Array */
if (typeof path === ‘string’) path = [path]

Vue.extend

if (process.env.NODE_ENV !== ‘production’) {
assert(Array.isArray(path), `module path must be a string or an
Array.`)
assert(path.length > 0, ‘cannot register the root module by using
registerModule.’)
}

Vue.component = function(){}

/*注册*/
this._modules.register(path, rawModule)
/*初始化module*/
installModule(this, this.state, path, this._modules.get(path))
// reset store to update getters…
/*
通过vm重设store,新建Vue对象使用Vue内部的响应式落成登记state以及computed
*/
resetStoreVM(this, this.state)
}
“`

Vue.directive = function(){}

registerModule用以注册3个动态模块,也正是在store制造现在再登记模块的时候用该接口。内部贯彻实际上也唯有installModule与resetStoreVM三个步骤,前边早已讲过,那里不再累述。

Vue.filter = function(){}

### unregisterModule

Vue.prototype.$isServer

“`javascript
/* 注销一个动态module */
unregisterModule (path) {
/* 转化称Array */
if (typeof path === ‘string’) path = [path]

Vue.version = ‘VERSION

if (process.env.NODE_ENV !== ‘production’) {
assert(Array.isArray(path), `module path must be a string or an
Array.`)
}

下2个正是 web-runtime.js 文件了,web-runtime.js 文件根本做了③件事儿:

/*注销*/
this._modules.unregister(path)
this._withCommit(() => {
/* 获取父级的state */
const parentState = getNestedState(this.state, path.slice(0, -1))
/* 从父级中去除 */
Vue.delete(parentState, path[path.length – 1])
})
/* 重制store */
resetStore(this)
}
“`

一、覆盖 Vue.config 的质量,将其设置为平台湾特务有的一对主意

同一,与registerModule对应的方法unregisterModule,动态注销模块。达成格局是先从state中剔除模块,然后用resetStore来重制store。

2、Vue.options.directives 和 Vue.options.components
安装平台湾特务有的授命和零部件

### resetStore

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

“`javascript
/* 重制store */
function resetStore (store, hot) {
store._actions = Object.create(null)
store._mutations = Object.create(null)
store._wrappedGetters = Object.create(null)
store._modulesNamespaceMap = Object.create(null)
const state = store.state
// init all modules
installModule(store, state, [], store._modules.root, true)
// reset vm
resetStoreVM(store, state, hot)
}
“`

通过 web-runtime.js 文件从此,Vue 产生下边那一个样子:

此处的resetStore其实约等于将store中的_actions等开始展览开头化未来,重新试行installModule与resetStoreVM来早先化module以及用Vue天性使其“响应式化”,那跟构造函数中的是一样的。

// 安装平台湾特务定的utils

## 插件

Vue.config.isUnknownElement = isUnknownElement

Vue提供了3个要命好用的插件[Vue.js
devtools]()

Vue.config.isReservedTag = isReservedTag

“`javascript
/* 从window对象的__VUE_DEVTOOLS_GLOBAL_HOOK__中获取devtool插件
*/
const devtoolHook =
typeof window !== ‘undefined’ &&
window.__VUE_DEVTOOLS_GLOBAL_HOOK__

Vue.config.getTagNamespace = getTagNamespace

export default function devtoolPlugin (store) {
if (!devtoolHook) return

Vue.config.mustUseProp = mustUseProp

/* devtoll插件实例存储在store的_devtoolHook上 */
store._devtoolHook = devtoolHook

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

/*
出发vuex的先河化事件,并将store的引用地址传给deltool插件,使插件获取store的实例
*/
devtoolHook.emit(‘vuex:init’, store)

Vue.options = {

/* 监听travel-to-state事件 */
devtoolHook.on(‘vuex:travel-to-state’, targetState => {
/* 重制state */
store.replaceState(targetState)
})

components: {

/* 订阅store的变化 */
store.subscribe((mutation, state) => {
devtoolHook.emit(‘vuex:mutation’, mutation, state)
})
}
“`

KeepAlive,

壹经已经设置了该插件,则会在windows对象上揭示三个__VUE_DEVTOOLS_GLOBAL_HOOK__。devtoolHook用在初阶化的时候会接触“vuex:init”事件通报插件,然后通过on方法监听“vuex:travel-to-state”事件来重新载入参数state。最终经过Store的subscribe方法来加多1个订阅者,在触发commit方法修改mutation数据之后,该订阅者会被打招呼,从而触发“vuex:mutation”事件。

Transition,

## 最后

TransitionGroup

Vuex是1个1二分可观的库,代码量不多且布局清晰,极度适合研讨学习其内部贯彻。近日的一体系源码阅读也使本人要好收益匪浅,写那篇小说也期望得以协助到越来越多想要学习研讨Vuex内部贯彻原理的同窗。

},

## 关于

directives: {

作者:染陌

model,

Email:answershuto@gmail.com or answershuto@126.com

show

Github:
[)

},

Blog:[)

filters: {},

搜狐主页:[)

_base: Vue

新浪专栏:[)

}

掘金:
[)

Vue.prototype.patch

osChina:[)

Vue.prototype.$mount

转发请申明出处,多谢。

此地要小心的是Vue.options 的变化。

迎接关怀本身的公众号

最终1个管理 Vue 的文书就是进口文件 web-runtime-with-compiler.js
了,该文件做了两件事:
壹、缓存来自 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_modules\vue\src\core\instance\init.js
文件中被声称的:
Vue.prototype._init = function (options?: Object) {

const vm: Component = this

// a uid

vm._uid = uid++

let startTag, endTag

/* istanbul ignore if */

if (process.env.NODE_ENV !== ‘production’ && config.performance &&
mark) {

startTag = vue-perf-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第1步要做的事务,使用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()
函数第2行就定义了,是指向_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

}

第三二行,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)时要举办统1,相关文章:

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图