Skip to content

Commit b31c205

Browse files
committed
feat: 新增实例成员初始化、Vue整体初始化调试和首次渲染调试注释说明
1 parent 88ccfe1 commit b31c205

File tree

11 files changed

+111
-5
lines changed

11 files changed

+111
-5
lines changed

Diff for: .vscode/bookmarks.json

+36-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
{
5757
"line": 9,
5858
"column": 24,
59-
"label": "Vue构造函数出处"
59+
"label": "Vue构造函数出处,实例成员初始化"
6060
}
6161
]
6262
},
@@ -69,6 +69,41 @@
6969
"label": "Vue静态成员的初始化"
7070
}
7171
]
72+
},
73+
{
74+
"path": "src/shared/util.js",
75+
"bookmarks": [
76+
{
77+
"line": 0,
78+
"column": 11,
79+
"label": "该文件的工具函数值得学习!"
80+
}
81+
]
82+
},
83+
{
84+
"path": "src/core/instance/init.js",
85+
"bookmarks": [
86+
{
87+
"line": 17,
88+
"column": 53,
89+
"label": "Vue.prototype._init方法"
90+
}
91+
]
92+
},
93+
{
94+
"path": "src/core/instance/state.js",
95+
"bookmarks": [
96+
{
97+
"line": 39,
98+
"column": 7,
99+
"label": "props和data,this.XXX属性返回值的源头"
100+
},
101+
{
102+
"line": 49,
103+
"column": 43,
104+
"label": "初始化 vm 的 _props, methods, _data, computed, watch,并把它们挂载到 vm 实例上,相关属性做响应式处理"
105+
}
106+
]
72107
}
73108
]
74109
}

Diff for: src/core/instance/events.js

+7
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ import {
1010
import { updateListeners } from '../vdom/helpers/index'
1111

1212
export function initEvents (vm: Component) {
13+
// 创建一个原型为 null 的对象
14+
// 用于存储事件对应的事件名和事件处理函数
15+
// 对象的属性就是 事件名, 属性值就是 事件处理函数 (数组的形式)
16+
// 调用 $on 方法时, 会将事件存储到 _events 对象中
1317
vm._events = Object.create(null)
1418
vm._hasHookEvent = false
1519
// init parent attached events
20+
// 获取父元素上附加的事件
1621
const listeners = vm.$options._parentListeners
1722
if (listeners) {
23+
// 注册自定义事件, 将父元素附加的事件注册到当前组件上
1824
updateComponentListeners(vm, listeners)
1925
}
2026
}
@@ -49,6 +55,7 @@ export function updateComponentListeners (
4955
target = undefined
5056
}
5157

58+
// 下边这几个方法的实现借助了“观察者模式”!
5259
export function eventsMixin (Vue: Class<Component>) {
5360
const hookRE = /^hook:/
5461
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {

Diff for: src/core/instance/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { lifecycleMixin } from './lifecycle'
66
import { warn } from '../util/index'
77

88
// 此处不使用 class 是为了后续方便给 Vue 实例混入实例成员,表现为方便往Vue的prototype上添加方法或者属性
9+
// 不是说使用 class 不行,而是如此使用后会显得代码风格不是很一致(Vue 类 和 Vue.prototype 用法)
910
function Vue (options) {
1011
if (process.env.NODE_ENV !== 'production' &&
1112
!(this instanceof Vue)
@@ -20,7 +21,7 @@ function Vue (options) {
2021
initMixin(Vue)
2122
// 注册 vm 的 $data/$props/$set/$delete/$watch
2223
stateMixin(Vue)
23-
// 初始化事件相关的方法:$on/$off/$emit
24+
// 初始化事件相关的方法:$on/$once/$off/$emit
2425
eventsMixin(Vue)
2526
// 初始化生命周期相关的混入方法
2627
// _update/$forceUpdate/$destroy

Diff for: src/core/instance/init.js

+18
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,28 @@ let uid = 0
1414

1515
export function initMixin (Vue: Class<Component>) {
1616
// 给Vue的原型上添加 _init 方法
17+
// 合并 options / 初始化操作
1718
Vue.prototype._init = function (options?: Object) {
1819
const vm: Component = this
1920
// a uid
21+
// 唯一标识符
2022
vm._uid = uid++
2123

24+
// 开发环境下性能检测相关 ------------------------
2225
let startTag, endTag
2326
/* istanbul ignore if */
2427
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
2528
startTag = `vue-perf-start:${vm._uid}`
2629
endTag = `vue-perf-end:${vm._uid}`
2730
mark(startTag)
2831
}
32+
// ----------------------------------------------
2933

3034
// a flag to avoid this being observed
35+
// 如果是 Vue 实例,则不需要被 observe
3136
vm._isVue = true
3237
// merge options
38+
// 将用户传入的options与Vue本身实例化过程中创建的options进行合并
3339
if (options && options._isComponent) {
3440
// optimize internal component instantiation
3541
// since dynamic options merging is pretty slow, and none of the
@@ -42,6 +48,8 @@ export function initMixin (Vue: Class<Component>) {
4248
vm
4349
)
4450
}
51+
52+
// 渲染时才会用到这里的 _renderProxy 方法
4553
/* istanbul ignore else */
4654
if (process.env.NODE_ENV !== 'production') {
4755
initProxy(vm)
@@ -50,13 +58,23 @@ export function initMixin (Vue: Class<Component>) {
5058
}
5159
// expose real self
5260
vm._self = vm
61+
// vm 的生命周期相关变量的初始化:$parent/$children/$refs/$root
5362
initLifecycle(vm)
63+
// vm 的事件监听初始化,父组件绑定在当前组件上的事件
5464
initEvents(vm)
65+
// vm 的编译render初始化
66+
// $slots/$scopedSlots/_c/$createElement/$attrs/$listeners
5567
initRender(vm)
68+
// beforeCreate 生命函数钩子的回调
5669
callHook(vm, 'beforeCreate')
70+
// 把 inject 的成员注入到 vm 上
71+
// 与下边的 initProvide 是一对, 用于实现组件之间的依赖注入
5772
initInjections(vm) // resolve injections before data/props
73+
// 初始化 vm 的 _props/methods/_data/computed/watch
5874
initState(vm)
75+
// 初始化 provide, 需要在加载完 data/props 之后才初始化 provide
5976
initProvide(vm) // resolve provide after data/props
77+
// created 生命函数钩子的回调
6078
callHook(vm, 'created')
6179

6280
/* istanbul ignore if */

Diff for: src/core/instance/inject.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import { defineReactive, toggleObserving } from '../observer/index'
77
export function initProvide (vm: Component) {
88
const provide = vm.$options.provide
99
if (provide) {
10+
// vm._provided 这个属性在 initInjections 中会使用到, 用于判断 inject 对象的属性是否在于该对象中
1011
vm._provided = typeof provide === 'function'
11-
? provide.call(vm)
12-
: provide
12+
? provide.call(vm) // 如果是函数, 调用 provide, 并将 函数返回值保存到 _provided 中
13+
: provide // 如果是对象, 直接将对象保存到 _provided
1314
}
1415
}
1516

17+
// inject 选项需要与 provide 选项一起使用
1618
export function initInjections (vm: Component) {
19+
// 获取当前组件中的 inject 对象的属性
20+
// 这些 inject 对象的属性必须要在 vm._provided 中存在
1721
const result = resolveInject(vm.$options.inject, vm)
1822
if (result) {
1923
toggleObserving(false)
@@ -29,6 +33,7 @@ export function initInjections (vm: Component) {
2933
)
3034
})
3135
} else {
36+
// 将 inject 的数据挂载到 vm 上, 并定义成响应式
3237
defineReactive(vm, key, result[key])
3338
}
3439
})
@@ -40,6 +45,7 @@ export function resolveInject (inject: any, vm: Component): ?Object {
4045
if (inject) {
4146
// inject is :any because flow is not smart enough to figure out cached
4247
const result = Object.create(null)
48+
// 获取 inject 中所有属性
4349
const keys = hasSymbol
4450
? Reflect.ownKeys(inject)
4551
: Object.keys(inject)
@@ -48,10 +54,14 @@ export function resolveInject (inject: any, vm: Component): ?Object {
4854
const key = keys[i]
4955
// #6574 in case the inject object is observed...
5056
if (key === '__ob__') continue
57+
// 在 global-api/extend.js => mergeOptions => normalizeInject(child, vm) 中
58+
// 将 inject 定义成 form: {from: "form"} 的格式
5159
const provideKey = inject[key].from
5260
let source = vm
5361
while (source) {
62+
// 判断 inject 的中的属性是否存在于 vm._provided 中
5463
if (source._provided && hasOwn(source._provided, provideKey)) {
64+
// 如果存在 , 保存到 result 中
5565
result[key] = source._provided[provideKey]
5666
break
5767
}

Diff for: src/core/instance/lifecycle.js

+7
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ export function initLifecycle (vm: Component) {
3838
while (parent.$options.abstract && parent.$parent) {
3939
parent = parent.$parent
4040
}
41+
42+
// 将 vm 添加到父组件的 $children 列表中
4143
parent.$children.push(vm)
4244
}
4345

@@ -56,6 +58,9 @@ export function initLifecycle (vm: Component) {
5658
}
5759

5860
export function lifecycleMixin (Vue: Class<Component>) {
61+
62+
// _update方法的作用是把 VNode 渲染成真实的DOM
63+
// 首次渲染会调用该方法,数据更新时也会
5964
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
6065
const vm: Component = this
6166
const prevEl = vm.$el
@@ -187,6 +192,8 @@ export function mountComponent (
187192
}
188193
} else {
189194
updateComponent = () => {
195+
// 第1个参数传入的是 render 属性 或者 template 转换成的 render 属性
196+
// vm._update 内部会将 虚拟DOM -> 真实DOM
190197
vm._update(vm._render(), hydrating)
191198
}
192199
}

Diff for: src/core/instance/proxy.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ if (process.env.NODE_ENV !== 'production') {
3434
)
3535
}
3636

37+
// isNative 位置在 src/core/util/env.js 中
3738
const hasProxy =
3839
typeof Proxy !== 'undefined' && isNative(Proxy)
3940

Diff for: src/core/instance/render-helpers/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { bindObjectListeners } from './bind-object-listeners'
1212
import { resolveScopedSlots } from './resolve-scoped-slots'
1313
import { bindDynamicKeys, prependModifier } from './bind-dynamic-keys'
1414

15+
// 以下导出的这些方法在模板编译为render方法的过程中会使用到!
1516
export function installRenderHelpers (target: any) {
1617
target._o = markOnce
1718
target._n = toNumber

Diff for: src/core/instance/render.js

+7
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ export function initRender (vm: Component) {
2828
// so that we get proper render context inside it.
2929
// args order: tag, data, children, normalizationType, alwaysNormalize
3030
// internal version is used by render functions compiled from templates
31+
// 对编译生成的 render 进行渲染的方法(模板编译时会看到对该_c方法的调用)
3132
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
3233
// normalization is always applied for the public version, used in
3334
// user-written render functions.
35+
// new Vue操作时,传入 render 方法的 h 就是这里的 $createElement
3436
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
3537

3638
// $attrs & $listeners are exposed for easier HOC creation.
@@ -88,6 +90,11 @@ export function renderMixin (Vue: Class<Component>) {
8890
// separately from one another. Nested component's render fns are called
8991
// when parent component is patched.
9092
currentRenderingInstance = vm
93+
// _render 方法最核心的一行代码
94+
// 调用 用户定义/模板渲染 render
95+
// render (h) { return h('div', 'hello world') }
96+
// vm.$createElement 就是参数 h
97+
// h 的作用就是生成虚拟 DOM
9198
vnode = render.call(vm._renderProxy, vm.$createElement)
9299
} catch (e) {
93100
handleError(e, vm, `render`)

Diff for: src/core/instance/state.js

+16
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const sharedPropertyDefinition = {
3535
set: noop
3636
}
3737

38+
// 对于 vm.$options.props 中的属性:this.XXX -> this._props.XXX
39+
// 对于 vm.$options.data 中的属性:this.XXX -> this._data.XXX
3840
export function proxy (target: Object, sourceKey: string, key: string) {
3941
sharedPropertyDefinition.get = function proxyGetter () {
4042
return this[sourceKey][key]
@@ -48,6 +50,7 @@ export function proxy (target: Object, sourceKey: string, key: string) {
4850
export function initState (vm: Component) {
4951
vm._watchers = []
5052
const opts = vm.$options
53+
// initProps 作用:将传入的 props 中的属性转换为响应式数据,并挂载到 vm 实例上
5154
if (opts.props) initProps(vm, opts.props)
5255
if (opts.methods) initMethods(vm, opts.methods)
5356
if (opts.data) {
@@ -103,6 +106,7 @@ function initProps (vm: Component, propsOptions: Object) {
103106
// during Vue.extend(). We only need to proxy props defined at
104107
// instantiation here.
105108
if (!(key in vm)) {
109+
// 将 key 挂载到 vm 实例上,同时存储到 vm._props 中
106110
proxy(vm, `_props`, key)
107111
}
108112
}
@@ -111,6 +115,7 @@ function initProps (vm: Component, propsOptions: Object) {
111115

112116
function initData (vm: Component) {
113117
let data = vm.$options.data
118+
// 初始化 _data, 组件中 data 是函数,调用函数返回结果,否则直接返回 data
114119
data = vm._data = typeof data === 'function'
115120
? getData(data, vm)
116121
: data || {}
@@ -123,10 +128,13 @@ function initData (vm: Component) {
123128
)
124129
}
125130
// proxy data on instance
131+
// 获取 data 中的所有树丛
126132
const keys = Object.keys(data)
133+
// props / methods
127134
const props = vm.$options.props
128135
const methods = vm.$options.methods
129136
let i = keys.length
137+
// 判断 data 中的成员是否和 props/methods 重名
130138
while (i--) {
131139
const key = keys[i]
132140
if (process.env.NODE_ENV !== 'production') {
@@ -144,6 +152,7 @@ function initData (vm: Component) {
144152
vm
145153
)
146154
} else if (!isReserved(key)) {
155+
// 将 key 挂载到 vm 实例上,同时存储到 vm._data 中
147156
proxy(vm, `_data`, key)
148157
}
149158
}
@@ -155,6 +164,8 @@ export function getData (data: Function, vm: Component): any {
155164
// #7573 disable dep collection when invoking data getters
156165
pushTarget()
157166
try {
167+
// 这里解释了为啥我们可以在 data 中通过 this.props.XX 的形式做赋值操作
168+
// 因为先 initProps 再 initData,并且 data 函数中的 this 指向了 vm 实例
158169
return data.call(vm, vm)
159170
} catch (e) {
160171
handleError(e, vm, `data()`)
@@ -283,6 +294,9 @@ function initMethods (vm: Component, methods: Object) {
283294
)
284295
}
285296
}
297+
298+
// 这里通过 bind, 让 methods 中方法的 this 指向 vm 实例
299+
// bind 实际位置:src/shared/util.js
286300
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
287301
}
288302
}
@@ -336,6 +350,8 @@ export function stateMixin (Vue: Class<Component>) {
336350
warn(`$props is readonly.`, this)
337351
}
338352
}
353+
354+
// 不允许直接修改 $data 和 $props 这两个属性(如果直接修改了会报上述的警告信息)
339355
Object.defineProperty(Vue.prototype, '$data', dataDef)
340356
Object.defineProperty(Vue.prototype, '$props', propsDef)
341357

Diff for: src/platforms/web/entry-runtime-with-compiler.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ Vue.prototype.$mount = function (
5454
}
5555
}
5656
} else if (template.nodeType) {
57+
// 如果传入的就是一个 html 元素了,则直接拿 template。innerHTML 作为模板
5758
template = template.innerHTML
5859
} else {
5960
if (process.env.NODE_ENV !== 'production') {
@@ -62,14 +63,16 @@ Vue.prototype.$mount = function (
6263
return this
6364
}
6465
} else if (el) {
66+
// 如果没有 template,获取 el 的 outerHTMl 作为模板
6567
template = getOuterHTML(el)
6668
}
6769
if (template) {
6870
/* istanbul ignore if */
6971
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
7072
mark('compile')
7173
}
72-
74+
75+
// 把 template 转换为 render 函数
7376
const { render, staticRenderFns } = compileToFunctions(template, {
7477
outputSourceRange: process.env.NODE_ENV !== 'production',
7578
shouldDecodeNewlines,

0 commit comments

Comments
 (0)