投身烈火
Keep Walking

vue源码阅读笔记(7)

书接上文,话说我自己都不记得上文是啥了……嗯,总之接着读吧。

Vue构造函数和vue实例 instance

上次看到Vue构造函数和vue实例的模块,整个模块的入口 index.js ,还看了 init.js ,今天我才发现,原来除了入口 index.js ,模块中其他文件其实都是分为了 mixin 和 init 两部分,mixin的部分是为了给Vue构造函数原型链上挂方法的,init部分是给实例init的过程中调用,给实例附加属性的,其实都是给实例服务的,所以把两部分混合在一起因为这个吧……╮( ̄▽ ̄)╭……后续再按文件阅读的时候,我会对这单独注明。

lifecycle.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/* @flow */
import config from '../config'
import { perf } from '../util/perf'
import Watcher from '../observer/watcher'
import { createEmptyVNode } from '../vdom/vnode'
import { observerState } from '../observer/index'
import { updateComponentListeners } from './events'
import { resolveSlots } from './render-helpers/resolve-slots'
import {
warn,
noop,
remove,
handleError,
emptyObject,
validateProp
} from '../util/index'
// 用来保存当前活动节点,其他模块中会用到
export let activeInstance: any = null
// 实例init部分
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
// 下面这段逻辑是为了找到距离当前组件最近的非抽象组件,话说也不知道我翻译的对不对
// 啥叫抽象组件?在vue文档里找到的解释是,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
// 具体的实现方法是在options里面加一个属性abstract,但是这个属性,文档中没有记载
// 比如keep-alive和transition,就是都是抽象组件。
// 所以这段逻辑是用来将抽象组件从组件链中的去除用的
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
// 设置父组件
// https://cn.vuejs.org/v2/api/#vm-parent
vm.$parent = parent
// 设置根组件
// https://cn.vuejs.org/v2/api/#vm-root
vm.$root = parent ? parent.$root : vm
// 设置子组件
// https://cn.vuejs.org/v2/api/#vm-children
vm.$children = []
// 设置引用
// https://cn.vuejs.org/v2/api/#vm-refs
vm.$refs = {}
// 下面都是内部属性
// 这是貌似是当前组件的观察者,触发watch做的
vm._watcher = null
// 用来标记组件是否停用的貌似,用来配合keep-live组件用的
vm._inactive = null
// 字面上翻译是直接停用,用来配合keep-live组件用的
vm._directInactive = false
// 用来标记组件是否已渲染
vm._isMounted = false
// 用来标记组件是否已销毁
vm._isDestroyed = false
// 用来标记组件是否正在被销毁,为了防止组件被重复销毁
vm._isBeingDestroyed = false
}
// 构造函数mixin部分
export function lifecycleMixin (Vue: Class<Component>) {
// 内部方法,文档中没写,实例的数据更新方法
// hydrating这个属性我查了好久,字面上是水合作用的意思
// 我就奇了怪了,读个代码咋还跟化学扯上关系了……
// 读了半天感觉像是让dom和虚拟dom融合的,也不知道对不对……
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
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
// 上面这段说 __patch__ 是后端渲染注入的入口?没懂啥意思
// 总之__path__其实就是补丁算法
if (!prevVnode) {
// initial render
// 如果没有prevVnode,那就初始化渲染,其实就是创建真实的dom节点
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
} else {
// updates
// 如果有prevVnode,就更新
vm.$el = vm.__patch__(prevVnode, vnode)
}
// 更新完了,把活动节点还回去
activeInstance = prevActiveInstance
// update __vue__ reference
// 更新vue引用,原来还是会在dom上绑定数据呀
// 不过这个数据貌似在实际中并没有用到呢
// 对于调试还挺有意义,在dom节点上能找到对应的vue对象
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
// 如果parent是个高阶组件,直接更新他的$el属性
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
// 上面这段貌似是说,这个函数里没有调用update的钩子函数,是因为有统一的调度程序
// 这样能确保子组件都更新了,貌似吧
}
// 强制重新渲染方法
// https://cn.vuejs.org/v2/api/#vm-forceUpdate
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
// 居然是直接用watcher来更新……
if (vm._watcher) {
vm._watcher.update()
}
}
// 销毁方法
// https://cn.vuejs.org/v2/api/#vm-destroy
Vue.prototype.$destroy = function () {
const vm: Component = this
// 利用标记判断是否已经开始销毁
if (vm._isBeingDestroyed) {
return
}
// 钩子调完了就算开始销毁了
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
// remove self from parent
// 先把自己从组件链中删除
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
// 然后把watcher停了
if (vm._watcher) {
vm._watcher.teardown()
}
// 清除_watchers队列,貌似和_watcher还不是一回事儿,
// _watcher是用来更新自己的,_watchers是用来记录计算属性的
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// remove reference from data ob
// frozen object may not have observer.
// 删除绑定对象的引用
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
// 到这儿就算销毁完了
vm._isDestroyed = true
callHook(vm, 'destroyed')
// turn off all instance listeners.
// 最后注销所有的事件
vm.$off()
// remove __vue__ reference
// 把自己的引用删了
if (vm.$el) {
vm.$el.__vue__ = null
}
// invoke destroy hooks on current rendered tree
// 最后把节点删了
vm.__patch__(vm._vnode, null)
}
}
// 暴露的外部方法,对应vue.$mount
// 之所以没放mixin里面试因为要提供给不同入口
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
/* istanbul ignore if */
if (vm.$options.template && vm.$options.template.charAt(0) !== '#') {
warn(
'You are using the runtime-only build of Vue where the template ' +
'option is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
// 定义更新节点的函数,如果是测试环境会记录性能
if (process.env.NODE_ENV !== 'production' && config.performance && perf) {
updateComponent = () => {
const name = vm._name
const startTag = `start ${name}`
const endTag = `end ${name}`
perf.mark(startTag)
const vnode = vm._render()
perf.mark(endTag)
perf.measure(`${name} render`, startTag, endTag)
perf.mark(startTag)
vm._update(vnode, hydrating)
perf.mark(endTag)
perf.measure(`${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
}
// 这里给watcher赋值,可以看出是用watcher来执行update方法更新组件的
vm._watcher = new Watcher(vm, updateComponent, noop)
// watcher的响应函数一上来就会运行一次,是为了记录初始值,
// 然后立马就把这个水合的开关给关上了……估计是后续的更新都不用他了
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
// 上面说这里手动调用mounted的钩子是为了创建子组件?
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
// 更新子组件的方法,组件init的时候用的
export function updateChildComponent (
vm: Component,
propsData: ?Object,
listeners: ?Object,
parentVnode: VNode,
renderChildren: ?Array<VNode>
) {
// determine whether component has slot children
// we need to do this before overwriting $options._renderChildren
// 这段是判断有没有slot的
const hasChildren = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
parentVnode.data.scopedSlots || // has new scoped slots
vm.$scopedSlots !== emptyObject // has old scoped slots
)
//
vm.$options._parentVnode = parentVnode
vm.$vnode = parentVnode // update vm's placeholder node without re-render
if (vm._vnode) { // update child tree's parent
vm._vnode.parent = parentVnode
}
vm.$options._renderChildren = renderChildren
// update props
// 更新属性的逻辑
if (propsData && vm.$options.props) {
// 更新属性过程中先关闭监听
observerState.shouldConvert = false
if (process.env.NODE_ENV !== 'production') {
observerState.isSettingProps = true
}
const props = vm._props
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i]
props[key] = validateProp(key, vm.$options.props, propsData, vm)
}
// 属性更新之后再打开
observerState.shouldConvert = true
if (process.env.NODE_ENV !== 'production') {
observerState.isSettingProps = false
}
// keep a copy of raw propsData
// 把传进来的原始属性保存一份
vm.$options.propsData = propsData
}
// update listeners
// 更新事件监听
if (listeners) {
const oldListeners = vm.$options._parentListeners
vm.$options._parentListeners = listeners
// 这个是event.js里的方法,看到再说
updateComponentListeners(vm, listeners, oldListeners)
}
// resolve slots + force update if has children
// 更新slots,然后强制更新
if (hasChildren) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
vm.$forceUpdate()
}
}
// 判断组件链是否激活
function isInInactiveTree (vm) {
while (vm && (vm = vm.$parent)) {
if (vm._inactive) return true
}
return false
}
// 激活组件的方法,貌似是给keep-alive用的
export function activateChildComponent (vm: Component, direct?: boolean) {
// direct貌似是用来表示是否是直接调用的
if (direct) {
vm._directInactive = false
if (isInInactiveTree(vm)) {
// 如果是直接调用并且组件树被激活过,就返回了
return
}
} else if (vm._directInactive) {
// 如果不是直接调用但是已经停用了,也返回
return
}
if (vm._inactive || vm._inactive == null) {
vm._inactive = false
// 递归激活组件
for (let i = 0; i < vm.$children.length; i++) {
activateChildComponent(vm.$children[i])
}
// 触发钩子
callHook(vm, 'activated')
}
}
// 停用组件,逻辑与激活的类似
export function deactivateChildComponent (vm: Component, direct?: boolean) {
if (direct) {
vm._directInactive = true
if (isInInactiveTree(vm)) {
return
}
}
if (!vm._inactive) {
vm._inactive = true
for (let i = 0; i < vm.$children.length; i++) {
deactivateChildComponent(vm.$children[i])
}
callHook(vm, 'deactivated')
}
}
// 调用钩子函数的工具函数
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)
}
}

呼,终于看完了,这章节信息量还挺大的,一些常用的生命周期以及函数都有出现呢,总体感觉组件创建,销毁,更新的那部分逻辑其实可以作为标准逻辑来借鉴。

好的那么由于时间不足,本期的博客就写到这里了,如果不出意外的话,maybe可能也许大概下周五会更新吧,能不能准时更新,就全看米娜桑点赞转发安利留言的力度了~!

白了个白~!