投身烈火
Keep Walking

vue源码阅读笔记(6)

嗯,距离上次读vue的源码已经过了一个月了,再次捡起来已经完全不认得了……啊……俗话说,一天不练手脚慢,两天不练丢一半,三天不练门外汉,四天不练瞪眼看,俗话诚不欺我……总之写到哪儿算哪儿吧,希望能跟之前写的接上……╮( ̄▽ ̄)╭

Vue构造函数和vue实例 instance

上次把global api和config都看了一遍,这次准备开始看instance这部分,这部分主要定义的是Vue构造函数和vue实例的各种方法。之前在global api中加工的Vue构造函数就出自这个模块。

index.js

这个文件是整个模块的入口,主要负责将各个子模块的功能挂载到Vue构造函数上。挂载的方法与global api一样,通过Mixin函数将方法和属性附到Vue.prototype上。

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
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')
}
// _init就是Vue的起点
this._init(options)
}
// 挂载_init方法
initMixin(Vue)
// 挂载$set、$delete方法和$data、$prop属性
stateMixin(Vue)
// 挂载$on、$once、$off、$emit方法
eventsMixin(Vue)
// 挂载_update、$forceUpdate、$destroy方法
lifecycleMixin(Vue)
// 挂载$nextTick、_render方法
renderMixin(Vue)
export default Vue

可以看到_init是构造vue对象的起点,下面我们顺着mixin的挂载顺序,逐个分析文件。

init.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
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/* @flow */
import config from '../config'
import { perf } from '../util/perf'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { initInjections } from './inject'
import { initLifecycle, callHook } from './lifecycle'
import { extend, mergeOptions, formatComponentName } from '../util/index'
let uid = 0
export function initMixin (Vue: Class<Component>) {
// vue对象的初始化方法
Vue.prototype._init = function (options?: Object) {
/* istanbul ignore if 覆盖率工具istanbul的注释语法,下面的if语句不会计入覆盖率*/
if (process.env.NODE_ENV !== 'production' && config.performance && perf) {
perf.mark('init')
}
const vm: Component = this
// uid是用来作为vue对象的唯一标示用的
vm._uid = uid++
// 避免vue对象被观测的标记
vm._isVue = true
// 合并options
if (options && options._isComponent) {
// 如果是组件就走这个逻辑,initInternalComponent函数合并options的速度要比mergeOptions更快
initInternalComponent(vm, options)
} else {
// 实例化vue类的时候会调到这里,就是直接 new Vue 或者用 Vue.extend 扩展的子类
// 用之前也说过mergeOptions主要用于扩展和继承
// 这里用来整理当前的options
vm.$options = mergeOptions(
// 这里获取类的options
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm)
} else {
vm._renderProxy = vm
}
// 把vm暴露出来
vm._self = vm
// 启动在index里面挂载的各个模块
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initState(vm)
initInjections(vm)
callHook(vm, 'created')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && perf) {
vm._name = formatComponentName(vm, false)
perf.mark('init end')
perf.measure(`${vm._name} init`, 'init', 'init end')
}
if (vm.$options.el) {
// options有el参数的时候会自动执行绑定函数
vm.$mount(vm.$options.el)
}
}
}
// 组件的初始化使用的函数,效率比使用mergeOptions效率高
// 因为函数内只赋值了特定的属性
function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
opts.parent = options.parent
opts.propsData = options.propsData
opts._parentVnode = options._parentVnode
opts._parentListeners = options._parentListeners
opts._renderChildren = options._renderChildren
opts._componentTag = options._componentTag
opts._parentElm = options._parentElm
opts._refElm = options._refElm
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
// 整理获取类的options
export function resolveConstructorOptions (Ctor: Class<Component>) {
let options = Ctor.options
if (Ctor.super) {
// 如果有父类,就整理父类的options
const superOptions = resolveConstructorOptions(Ctor.super)
const cachedSuperOptions = Ctor.superOptions
if (superOptions !== cachedSuperOptions) {
// 如果父类的options有变化,需要重新获取父类的options
Ctor.superOptions = superOptions
// resolveModifiedOptions可以检查options的更新
const modifiedOptions = resolveModifiedOptions(Ctor)
// 同步修改过的属性
if (modifiedOptions) {
extend(Ctor.extendOptions, modifiedOptions)
}
options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
if (options.name) {
options.components[options.name] = Ctor
}
}
}
return options
}
// 检查options的更新
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = dedupe(latest[key], sealed[key])
}
}
return modified
}
// 删除重复数据的函数
function dedupe (latest, sealed) {
// 主要是处理生命周期回调,让回调不会直接被替换赋值,而是被逐个添加
if (Array.isArray(latest)) {
const res = []
sealed = Array.isArray(sealed) ? sealed : [sealed]
for (let i = 0; i < latest.length; i++) {
if (sealed.indexOf(latest[i]) < 0) {
res.push(latest[i])
}
}
return res
} else {
return latest
}
}

这个模块对于 vue 实例的处理有很多都可以在官方文档的说明中找到,文档中没有描述的是 options 对象的继承。可以看到 options 对象的继承和并不是基于原型链的,而是基于合并赋值,我猜这么做的目的是为了让程序运行起来更快,毕竟省了一个遍历原型链的过程……

proxy.js

proxy 这部分的启动逻辑只有在非生产环境才能起作用,感觉不是很重要啊……主要的作用是用来检查vm添加的属性是不是系统变量,如果是系统变量就报警告,貌似是这样吧……Proxy方法是用来定义对象属性的基本操作的,感觉跟defineProperty有点像

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
//flow无法检查原生对象Proxy,所有这个文件就没用flow
import config from 'core/config'
import { warn, makeMap } from '../util/index'
let initProxy
if (process.env.NODE_ENV !== 'production') {
const allowedGlobals = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require' // for Webpack/Browserify
)
const warnNonPresent = (target, key) => {
warn(
`Property or method "${key}" is not defined on the instance but ` +
`referenced during render. Make sure to declare reactive data ` +
`properties in the data option.`,
target
)
}
const hasProxy =
typeof Proxy !== 'undefined' &&
Proxy.toString().match(/native code/)
if (hasProxy) {
const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta')
config.keyCodes = new Proxy(config.keyCodes, {
set (target, key, value) {
if (isBuiltInModifier(key)) {
warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`)
return false
} else {
target[key] = value
return true
}
}
})
}
const hasHandler = {
has (target, key) {
const has = key in target
const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
if (!has && !isAllowed) {
warnNonPresent(target, key)
}
return has || !isAllowed
}
}
const getHandler = {
get (target, key) {
if (typeof key === 'string' && !(key in target)) {
warnNonPresent(target, key)
}
return target[key]
}
}
initProxy = function initProxy (vm) {
if (hasProxy) {
// determine which proxy handler to use
const options = vm.$options
// _withStripped这个属性如果被设为true,则不能给vm添加系统变量作为属性,貌似吧……具体的用法是在测试代码里找到的……
const handlers = options.render && options.render._withStripped
? getHandler
: hasHandler
vm._renderProxy = new Proxy(vm, handlers)
} else {
vm._renderProxy = vm
}
}
}
export { initProxy }

今天就到这儿吧,累了,话说下周开始我们也要007了……真是嘴贱某次跟领导吃饭的时候把007当笑话告诉了他,结果给他打开了新世界的大门……如果不出意外的话,maybe可能也许大概下周五会更新吧,能不能准时更新,就全看米娜桑点赞转发安利留言的力度了~!

白了个白~!