投身烈火
Keep Walking

vue源码阅读笔记(5)

五一好好给自己放了个假,去成都玩儿了几天。话说成都的生活可真是悠闲,搞得我都向去成都买房定居了~而且成都的吃的可真是,(¯﹃¯)…… 一想到猪肉锅盔和冒节子肥肠粉我口水就开始往外涌……咳咳……言归正传,上次说这次要读core的全部全局api和config,下面咱们就开始。

use.js

这个文件是用来给构造函数Vue挂载use方法用的,逻辑很简单。use方法是用来安装插件用的,对应文档

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
import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 安装过的控件不再安装
/* istanbul ignore if */
if (plugin.installed) {
return
}
// 组织要传给控件的参数,Vue构造函数作为第一个参数
const args = toArray(arguments, 1)
args.unshift(this)
// 根据plugin的类型使用不同调用方法,文档中只介绍了使用install属性来启动插件的方法,
// 如果plugin本身就是个函数,则使用这个函数来安装插件
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
// 给安装过的控件加锁
plugin.installed = true
return this
}
}

mixin.js

mixin的逻辑主要隐藏在mergeOptions函数里,之前咱么你说过,这个函数是组件实例化和继承的核心方法,其实就是根据不同的属性进行合并,有规定的属性按照规定的规则,没规定的属性直接覆盖。等到读到 src/core/util/options.js 时再详细解读 mergeOptions 函数。对应文档

1
2
3
4
5
6
7
8
9
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
// 挂载mixin方法
Vue.mixin = function (mixin: Object) {
// 使用 mergeOptions 函数合并 Vue.options
this.options = mergeOptions(this.options, mixin)
}
}

extend.js

extend本身是用来创建子类的,内部使用mergeOptions来合并属性。对应文档

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import config from '../config'
import { warn, extend, mergeOptions } from '../util/index'
import { defineComputed, proxy } from '../instance/state'
export function initExtend (Vue: GlobalAPI) {
/**
* 初始化Vue的cid,用来做Vue构造函数和其子类的缓存的索引,每个Vue构造函数的子类也都有cid
*/
Vue.cid = 0
let cid = 1
// 挂载extend方法
Vue.extend = function (extendOptions: Object): Function {
// 子类的options的容错处理
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
// 取缓存,缓存是绑在options对象上的,就是说同样的父类、同样的配置,就会走缓存生成相同的子类。
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production') {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
)
}
}
// 定义子类的构造函数
const Sub = function VueComponent (options) {
// 后面会将父类的prototype接到子类的上,就可以调用Vue的_init方法了
this._init(options)
}
// 实现类的继承
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// 初始化子类的props属性,这里使用了一个函数来封装遍历的过程
// initProps里对options.props的每个属都进行了初始化
if (Sub.options.props) {
initProps(Sub)
}
// 与initProps类似
if (Sub.options.computed) {
initComputed(Sub)
}
// 让子类也能使用extend、mixin、use方法
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// config._assetTypes里面是所有的控件类型,这些也都复制到子类上
config._assetTypes.forEach(function (type) {
Sub[type] = Super[type]
})
// 允许递归查找自己
if (name) {
Sub.options.components[name] = Sub
}
// 保存对super和自己当前的options的引用,实例化的时候用于检查options是否更新了
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// 添加缓存
cachedCtors[SuperId] = Sub
return Sub
}
}
// initProps 和 initComputed 的操作类似,通过代理调用Object.defineProperty
// 简单看了下代理的逻辑,感觉并没有减少Object.defineProperty的调用次数
// 只是使用一个固定的对象来减少生成对象的数量
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
defineComputed(Comp.prototype, key, computed[key])
}
}

assets.js

asset貌似应该翻译为资源,这个文件主要是用来初始化Vue的资源的,所谓的资源就是类似directive、filter、component之类的控件。

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
import config from '../config'
import { warn, isPlainObject } from '../util/index'
export function initAssetRegisters (Vue: GlobalAPI) {
// 注册Vue默认支持的资源,就是注册了 Vue.directive、Vue.component、Vue.filter方法
config._assetTypes.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
if (!definition) {
// 获取资源
return this.options[type + 's'][id]
} else {
// 注册资源
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production') {
if (type === 'component' && config.isReservedTag(id)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + id
)
}
}
// 对不同资源进行不同处理
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}

全局api这部分就算是看完了,总的来说这部分逻辑都不复杂,主要学习的还是机制吧,插件的机制,继承的机制,资源注册获取的机制,基本上算是教科书式的了吧,自己开发中都可以借鉴到的。

config.js

core/config.js 文件里面记录了所有Vue可使用的配置,有些在api文档上查的到,有的则查不到。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/* @flow */
import { no, noop, identity } from 'shared/util'
// 这部分是给flow检查Config中每个属性的类型用的,简单易懂,同事标注了属性是给哪部分用的
export type Config = {
// user(用户可配置的属性)
optionMergeStrategies: { [key: string]: Function };
silent: boolean;
productionTip: boolean;
performance: boolean;
devtools: boolean;
errorHandler: ?(err: Error, vm: Component, info: string) => void;
ignoredElements: Array<string>;
keyCodes: { [key: string]: number | Array<number> };
// platform(给不同平台用的属性)
isReservedTag: (x?: string) => boolean;
parsePlatformTagName: (x: string) => string;
isUnknownElement: (x?: string) => boolean;
getTagNamespace: (x?: string) => string | void;
mustUseProp: (tag: string, type: ?string, name: string) => boolean;
// internal(内部用的属性)
_assetTypes: Array<string>;
_lifecycleHooks: Array<string>;
_maxUpdateCount: number;
};
const config: Config = {
/**
* 用户属性在官方文档中都有详细的说明,我就不做过多解释了,简单翻译一下
* 自定义合并策略 (used in core/util/options)
*/
optionMergeStrategies: Object.create(null),
/**
* 日志的控制开关
*/
silent: false,
/**
* 启东市是否显示生产模式的提示信息,这个是根据构造时测参数决定的
*/
productionTip: process.env.NODE_ENV !== 'production',
/**
* 是否使用调试工具
*/
devtools: process.env.NODE_ENV !== 'production',
/**
* 是否显示性能数据
*/
performance: process.env.NODE_ENV !== 'production',
/**
* 异常处理接口,如果使用官方文档上有
*/
errorHandler: null,
/**
* 忽略某些自定义元素
*/
ignoredElements: [],
/**
* 给 v-on 自定义键位别名
*/
keyCodes: Object.create(null),
/**
* 检查标签是否是保留标签的方法,各个平台之间不一样,没错,需要是一个返回true、false的方法
* 具体检查方法可以在 src/platforms/ 里面找
*/
isReservedTag: no,
/**
* 检查标签是否是未知标签
*/
isUnknownElement: no,
/**
* 获取标签的命名空间,也可以理解为获取标签的类型吧,html、svg之类的
*/
getTagNamespace: noop,
/**
* 解析标签在平台中的名字……貌似对标签名做映射的
*/
parsePlatformTagName: identity,
/**
* 检查标签必须包含的属性,比如input标签必须有value属性之类的……
*/
mustUseProp: no,
/**
* 所有支持的资源的列表
*/
_assetTypes: [
'component',
'directive',
'filter'
],
/**
* 所有生命周期的列表
*/
_lifecycleHooks: [
'beforeCreate',
'created',
'beforeMount',
'mounted',
'beforeUpdate',
'updated',
'beforeDestroy',
'destroyed',
'activated',
'deactivated'
],
/**
* 更新数据的最大循环次数
*/
_maxUpdateCount: 100
}
export default config

这个文件本身是没有逻辑的,而且每个属性都有注释,我就简单翻一下注释。感觉flow只是挺好用的呢,作用和typescript类似,如果不用vscode做编辑器,用flow来代替typescript应该会更简单方便一些,如果用vscode,那么使用typescript应该会更方便。虽然不服,但是大微软只是不鸣则已一鸣惊人,vscode甩了atom不知道多少条街……跑题了跑题了,好的那么由于时间不足,本期笔记就先写到这里,如果不出意外的话,maybe可能也许大概下周五会更新吧,能不能准时更新,就全看米娜桑点赞转发安利留言的力度了~!

白了个白~!