投身烈火
Keep Walking

vue源码阅读笔记(4)

嗯,又到了一周一度的deadline……之前几次把杂七杂八的部分读完了,这次终于要开始读vue核心部分的代码了。从之前读过的 src/entries 模块中可以看到,vue核心的代码的大概可以分为 runtime 和 compiler 两部分。runtime 对应 src/core 模块,compiler 对应 src/compiler 模块,这次就从 src/core 模块开始读。

Vue核心模块 core

这个模块最后输出的就是一个Vue的构造函数,里面包含了组件系统、全局API、vue实例、对象属性监测系统、公共方法、虚拟dom、配置,这些模块。因为各个模块之间都有联系,单拎出来一个个看感觉没法看到全貌呀。所以这次换个方式试试。

1
2
3
4
5
6
7
8
9
10
11
/*
|- core
|- components
|- global-api
|- instance
|- observer
|- util
|- vdom
|- config.js
|- index.js
*/

从 src/entries/web-runtime.js 文件中可以看出,Vue 这个构造函数是从 src/core/index.js 导出的,这个文件的代码如下:

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
// Vue构造函数来自src/core/instance/index.js
// 通过initGlobalAPI这个函数添加全局api
initGlobalAPI(Vue)
// 是否运行在服务端的标记,对应文档中的 https://cn.vuejs.org/v2/api/?#vm-isServer
Object.defineProperty(Vue.prototype, '$isServer', {
get: isServerRendering
})
// __VERSION__ 是在 build/config.js 里面配置的,在构建的时候通过脚本替换为配置里的变量
Vue.version = '__VERSION__'
// 导出Vue构造函数
export default Vue

Vue.prototype.$isServer 和 Vue.version 比较简单就不展开了,Vue构造函数后面再看,先看全局api吧。

index.js

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import config from '../config'
import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import builtInComponents from '../components/index'
import {
warn,
extend,
nextTick,
mergeOptions,
defineReactive
} from '../util/index'
export function initGlobalAPI (Vue: GlobalAPI) {
// 首先配置 Vue.config ,在定义时使用了 Object.defineProperty
// 所以在设置 Vue.config 的时候是不能直接 Vue.config = {....}这样的,不然会报错,得一项一项设
const configDef = {}
configDef.get = () => config
if (process.env.NODE_ENV !== 'production') {
configDef.set = () => {
warn(
'Do not replace the Vue.config object, set individual fields instead.'
)
}
}
Object.defineProperty(Vue, 'config', configDef)
// Vue.util虽然暴露出来了,但是并不是公共api的一部分,所以用的时候要小心点儿
Vue.util = {
// 下面这些方法都来自 core/util/这个模块,这里先简单说下每个函数的用途,后续展开
// 输出错误信息用的,同事还能输出错误组件的名字和对应的vue文件地址
warn,
// 顾名思义,浅拷贝
extend,
// 用来合并实例option的,组件实例化和继承的核心方法
mergeOptions,
// 用来给对象定义响应属性
defineReactive
}
// 给实例添加属性,对应 https://cn.vuejs.org/v2/api/?#Vue-set
Vue.set = set
// 删除实例属性,对应 https://cn.vuejs.org/v2/api/?#Vue-delete
Vue.delete = del
// 对应 https://cn.vuejs.org/v2/api/?#Vue-nextTick,一个打包执行延迟任务的方法,采用Promise => MutationObserver => setTimeout(0)的退化设计
// 在读nextTick源码的时候发现几个有意思的点,本来想憋到写 src/core/util 的时候再写的,但是我实在是憋不住了哈哈
// 1. 在使用promise的时候,ios里(其实就是safari和uiwebview)如果直接用promise.resolve()触发一个then的话,
// 他不会立即执行,运行一个空的setTimeout之后,在就没问题了……相当神奇……
// 另外,我发现他的ios判断里没有判断safari,但是safari里也有这个问题,不知道是不是bug……
// 2. MutationObserver是通过观测dom元素的变化来触发事件回调,具体替代promise的方法等看到了再详细说吧
// 3. 还有一个细节,在执行一批回调的时候,用的是[].length=0来清空数组,而不是空数组赋值的方法,感觉是为了省内存,不用频繁gc
Vue.nextTick = nextTick
// options用来存定义过的组件的,支持component, directive, filter,支持的组件列表保存在_assetTypes里面
// 具体还没有细看,但是感觉是支持扩展的
Vue.options = Object.create(null)
config._assetTypes.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
// 用来给所有实例标记base构造函数用的属性
Vue.options._base = Vue
// 挂载keep-alive组件,https://cn.vuejs.org/v2/api/?#keep-alive
extend(Vue.options.components, builtInComponents)
// 挂载 Vue.use 方法
initUse(Vue)
// 挂载 Vue.mixin 方法
initMixin(Vue)
// 挂载 Vue.extend 方法
initExtend(Vue)
// 挂载 Vue.component, Vue.directive, Vue.filter 方法
initAssetRegisters(Vue)
}

全局api的挂载逻辑还算相对简单,Vue的方法挂载每部分都拆成了单独的函数,利用js中Function是对象、可以作为参数的特性、是引用类型等特性,用这种函数来加工Vue构造函数,不知道这算不算装饰模式呢……

另外我发现一个细节,源码中在生成空对象的时候,会用Object.create(null)来生成,具体原因不明,因为这两种方式,除了原型不一样,其他的没区别,因为也见到有使用字面量来生成空对象的,所以会不会是因为是开源项目,开源项目中大量人员参与其中,每个人的编码习惯有差异造成的呢……

好的那么由于时间不足,本期笔记就先写到这里,话说每次只看这么一点儿,我还真是感觉惭愧呢,希望未来可以挤出更多的时间来读吧……如果不出意外的话,maybe可能也许大概下周五会更新吧,下周准备更新config和全部全局api,能不能准时更新,就全看米娜桑点赞转发安利留言的力度了~、

白了个白~!