vue源码分析(三) 整体架构
# 1. 概述
在上一章——vue源码分析(二) 源码构建 (opens new window),中我们分析项目的入口,接下来我们从项目入口开始分析 Vue
源码的整体结构。
# 2. 项目入口
我们以 npm run dev
为例,执行这行命令其实就是执行 package.json
中的 "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
这行。我们可以上一章的分析清楚的知道最终执行的是 config.js
文件,调用的是 genConfig(process.env.TARGET)
函数。
web-full-dev
,它的 entry
是 resolve('web/entry-runtime-with-compiler.js')
,分析到这里,我们知道了项目的入口是 web/entry-runtime-with-compiler.js
,下面我们看看这个文件。
源码目录: src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'
/*...*/
const mount = Vue.prototype.$mount // 缓存 runtime 中的 $mount 方法
Vue.prototype.$mount = function () {
/**
* el:真实的dom
* hydrating:undefined
* mount:定义在 src/platforms/web/runtime/index.js 中
*/
return mount.call(this, el, hydrating)
} // 重写 $mount 方法
2
3
4
5
6
7
8
9
10
11
12
13
从上面的代码我们可以知道,最终执行的是 const mount = Vue.prototype.$mount
,即 Vue
原型方法 $mount
,他定义在 ./runtime/index
,如下:
源码目录: src/platforms/web/runtime/index.js
import Vue from 'core/index'
/*...*/
// 原型上声明的 $mount方法在,这个方法会被runtime only版本和runtime compiler版本中复用
Vue.prototype.$mount = function (
el?: string | Element, // 真实的dom 或者 是string
hydrating?: boolean // 新的虚拟dom vnode
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
/*...*/
2
3
4
5
6
7
8
9
10
11
这里是给 Vue
原型上添加 $mount
方法,我们可一得到 Vue
构造函数是通过 import Vue from 'core/index'
导入的,所以 Vue
构造函数定义在 'core/index'
,下面我们分析一下 Vue
的构造函数。
# 3.Vue构造函数
源码目录: src/core/index.js
import Vue from './instance/index'
/*...*/
initGlobalAPI(Vue) // 初始化全局的API
/*...*/
2
3
4
5
通过这段代码我们知道,Vue
构造函数最终被定义在核心模块中,与平台无关。
说明: initGlobalAPI
函数我们下一章详细分析,这里就不多做说明!
定义如下:
源码目录: src/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'
// Vue 构造函数
function Vue (options) {
// 生产环境下,没有使用 new 创建实例会报下面警告
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) // 初始化Vue
stateMixin(Vue) // 数据绑定
eventsMixin(Vue) // 初始化事件
lifecycleMixin(Vue) // 初始化vue 生命周期: 更新 销毁
renderMixin(Vue) // 初始化渲染的函数
export default Vue
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
这段源码主要的做了一下几件事:
(1)、定义Vue 构造函数。
(2)、初始化Vue。
(3)、数据绑定 。
(4)、初始化事件。
(5)、初始化vue 生命周期 。
(6)、初始化渲染的函数 。
源码分析到这里,我们终于找到了Vue的构造函数的定义,在分析Vue构造函数之前,我们先带着一个问题去分析源码:new Vue (opens new window) 发生了什么?
# 3.1 new
在 JavaScript
中 new
主要做了一下几件事:
(1)、创建一个空对象,并且 this
变量引用该对象,同时还继承了该函数的原型。
(2)、属性和方法被加入到 this
引用的对象中。
(3)、新创建的对象由 this
所引用,并且最后隐式的返回 this
。
// Javascript的new关键字主要的作用是继承
// new一共经历4个阶段
// 1、创建一个空对象
var obj = new Object();
// 2、设置原型链
// 此时便建立了obj对象的原型链
obj._proto_ = Object.prototype;
// 3、让Func的this指向obj,并执行Func函数体
Object.call(obj);
// 4、判断Func的返回值类型
// 如果是值类型,返回obj;
// 如果是引用类型,返回这个引用类型的对象。
return typeof result === 'obj'? result : obj;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3.2 Vue
源码目录: src/instance/index.js
// Vue 构造函数
function Vue (options) {
// 生产环境下,没有使用 new 创建实例会报下面警告
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)
}
2
3
4
5
6
7
8
9
10
可以看到 Vue
构造函数是一个很简洁的function
工厂模式声明的一个构造函数, Vue
只能通过 new
关键字初始化,然后会调用 this._init
方法。
# 4.初始化
源码目录:src/core/instance/init.js
/**
* 初始化vue
* @param {Vue构造器} Vue
*/
export function initMixin (Vue: Class<Component>) {
//初始化函数
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
/*
* 测试代码性能
* 参考:https://segmentfault.com/a/1190000014479800
*/
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
// 避免被响应式的标识
// 这里可以暂时理解新建observer实例就是让数据响应式
vm._isVue = true
// merge options
// 有子组件时,options._isComponent才会为true
if (options && options._isComponent) { //这是组件实例化时的分支
// optimize internal component instantiation
// since dynamic options merging is pretty slow, and none of the
// internal component options needs special treatment.
/*
* 优化内部组件实例化
* 因为动态选项合并非常慢,而且内部组件选项需要特殊处理
* 初始化内部组件
*/
initInternalComponent(vm, options)
} else { // 根Vue实例执行到这里
// EMPHASIS:(重点分析:合并options)传入的options和vue自身的options进行合并保存到vm.$options
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor), // 解析constructor上的options属性的
options || {},
vm
)
}
/*
* istanbul ignore else
* 参考:https://segmentfault.com/a/1190000014824359
*/
if (process.env.NODE_ENV !== 'production') {
// 初始化代理监听
initProxy(vm)
} else {
vm._renderProxy = vm
}
// expose real self
vm._self = vm // 开放真实的self
initLifecycle(vm) // 初始化生命周期
initEvents(vm) // 初始化事件
initRender(vm) // 初始化渲染
// EMPHASIS:(生命周期:beforeCreate)
callHook(vm, 'beforeCreate') // 触发 beforeCreate 钩子函数
/**
* 在data / props之前解决注入问题
* 初始化 inject
*/
initInjections(vm) // resolve injections before data/props
// 初始化props属性、data属性、methods属性、computed属性、watch属性
initState(vm)
/**
* 在data / props初始化后解决注入问题
* 选项应该是一个对象或返回一个对象的函数
* 该对象包含可注入其子孙的属性,用于组件之间通信
*/
initProvide(vm) // resolve provide after data/props
// EMPHASIS:(生命周期:created)
callHook(vm, 'created') // 触发 created 钩子函数
/* istanbul ignore if */
//浏览器性能监听
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false)
mark(endTag)
measure(`vue ${vm._name} init`, startTag, endTag)
}
if (vm.$options.el) {
/**
* 手动挂载
* 在项目中可用于延时挂载(例如在挂载之前要进行一些其他操作、判断等),之后要手动挂载上
* new Vue时,el和 $mount 并没有本质上的不同
*/
vm.$mount(vm.$options.el)
}
}
}
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
Vue
初始化主要就干了几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data
、props
、computed
、watcher
,初始化 provide/inject
等,这里就不对它们做详细分析了,我们将在后面章节中进行详细分析。
分析到这里我们 Vue
框架的代码整理架构就分析完了。
# 5.总结