Skip to content

Commit 91e0573

Browse files
committed
chore: 新增getter和setter示例代码注释说明
1 parent b2a9f8f commit 91e0573

File tree

8 files changed

+74
-29
lines changed

8 files changed

+74
-29
lines changed

Diff for: .vscode/bookmarks.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@
144144
"label": "Observer 类"
145145
},
146146
{
147-
"line": 141,
147+
"line": 143,
148148
"column": 77,
149149
"label": "响应式处理入口"
150150
}
@@ -244,7 +244,7 @@
244244
"path": "src/core/observer/watcher.js",
245245
"bookmarks": [
246246
{
247-
"line": 115,
247+
"line": 116,
248248
"column": 20,
249249
"label": "watcher被作为 Dep.target 的执行代码"
250250
}

Diff for: examples/00-vue-analysis/13-reactive-object.html

+18-6
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,36 @@
77
</head>
88
<body>
99
<div id="app">
10-
<h2>{{ nested.a }}</h2>
10+
<!-- <h2>{{ nested.a }}</h2> -->
11+
<h2>{{ arr }}</h2>
1112
</div>
1213
<script src="../../dist/vue.js"></script>
1314
<script>
1415
// 1.响应式对象核心是利用 Object.defineProperty 给对象的属性添加 getter 和 setter
1516
// 2.Vue 内部会把 props、data 等变成响应式对象,在创建的过程中,发现子属性也为对象,则递归把该对象也变成响应式的,递归方式为 DFS(深度优先遍历)
1617
// 3.经过调试发现,响应式对象处理过程经过的代码流程有:
17-
// (1) 对于用户传入的 data 属性,且 data 属性返回的必须是一个 plain object,对于该对象中的每个属性
18-
// 如果值为对象,则 initMixin -> initState -> initData -> observe -> new Observer(给对象添加 __ob__ 属性)-> walk -> defineReactive
18+
// (1) 对于用户传入的 data 属性,且 data 属性返回的必须是一个 plain object,
19+
// 如果值为对象,则 initMixin -> initState -> initData -> observe -> new Observer(给对象添加 __ob__ 属性,标识该对象是响应式对象)-> walk -> defineReactive
1920
// 如果值为数组,则 initMixin -> initState -> initData -> observe -> new Observer -> observeArray -> observe
2021
// (2) 对于用户传入的 props 属性,则:initMixin -> initState -> initProps -> defineReactive
2122
new Vue({
2223
el: '#app',
2324
data () {
2425
return {
25-
nested: {
26-
a: 'i am 13-reactive-object'
27-
}
26+
// 值是对象
27+
// nested: {
28+
// a: 'i am 13-reactive-object'
29+
// }
30+
// 值是数组
31+
arr: [
32+
10,
33+
{
34+
name: 'kai'
35+
},
36+
// () => console.log('i am arrow fn!'),
37+
function () { console.log('i am common fn') },
38+
30
39+
]
2840
}
2941
}
3042
})

Diff for: examples/00-vue-analysis/14-getter.html

+16-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@
2323
// 进而会进入 defineReactive 函数的 getter 方法中(路径:src\core\observer\index.js),然后调用 dep.depend() 进行依赖收集的过程
2424
// 2.Watcher 实例化过程中会执行“this.get()”,对于该 get 方法,尤其注意其内部的 cleanupDeps 方法调用,即依赖清空的过程,这里以上边的例子为例,分析 cleanupDeps 方法设计的巧妙之处:
2525
// 上述模板中,会根据 v-if 和 flag 的值分别去渲染 msg 和 msg1 的值,
26-
// 当我们 flag 为 true 渲染 msg 的时候,会访问到 msg 变量对应的数据,这时候我们对 msg 使用的数据添加了 getter,做了依赖收集,那么当我们去修改 msg 数据的时候,理应通知到这些订阅者 Watcher 实例(例子中只有渲染Watcher);
27-
// 那么如果我们改变了条件,比如将 flag 改成 false(具体体现为调用了toggle方法将 flag 的值设置为 false),此时会渲染了 msg1 的内容,又会对 msg1 使用的数据添加了 getter,
28-
// 如果我们没有依赖移除的过程,即不执行 cleanupDeps 方法,那么这时候我去修改 msg 的数据(具体体现为调用了change方法改变了 msg 的值),还是会通知 msg 数据的订阅的回调,这显然是有浪费的。
29-
// 因此,Vue 设计了在每次添加完新的订阅,会移除掉旧的订阅(具体体现在 cleanupDeps 方法中),这样就保证了在我们刚才的场景中,如果渲染 msg1 模板的时候去修改 msg 的数据,msg 数据订阅回调(即Watcher实例)已经被移除了,所以不会有任何浪费
26+
// 当我们 flag 为 true 渲染 msg 的时候,会访问到 msg 变量对应的数据,这时候我们对 msg 使用的数据添加了 getter,做了依赖收集(此时会去收集依赖于 flag 和 msg 的 watcher),那么当我们去修改 msg 数据的时候,理应通知到这些订阅者 Watcher 实例(例子中只有渲染Watcher);
27+
// 那么如果我们改变了条件,比如将 flag 改成 false(具体体现为调用了toggle方法将 flag 的值设置为 false),此时会渲染了 msg1 的内容,又会对 msg1 使用的数据添加了 getter,此时会去收集依赖于 flag 和 msg1 的 watcher,
28+
// 如果我们没有依赖移除的过程,即不执行 cleanupDeps 方法,那么这时候我去修改 msg 的数据(具体体现为调用了change方法改变了 msg 的值),还是会通知 msg 数据的订阅的回调,因为这个订阅watcher还在 msg 对应的那个 dep 中,
29+
// 但由于此时这个渲染 Watcher 并不依赖于 msg 对应的那个 dep,让该依赖回调执行是浪费的。
30+
// 因此,Vue 设计了在每次添加完新的订阅,会移除掉旧的订阅(具体体现在 cleanupDeps 方法中),这样就保证了在我们刚才的场景中,
31+
// 如果渲染 msg1 模板的时候去修改 msg 的数据,msg 数据订阅回调(即Watcher实例)已经被移除了,所以不会有任何浪费。
3032
// 3.收集依赖的目的是为了当这些响应式数据发生变化,触发它们的 setter 的时候,能知道应该通知哪些订阅者(即Watcher)去做相应的逻辑处理,我们把这个过程叫派发更新
3133
// 4.依赖收集涉及代码调用流程:
3234
// defineReactive getter -> dep.depend -> Dep.target.addDep(this) -> dep.addSub(this)
@@ -37,6 +39,16 @@
3739
flag: true,
3840
msg: 'Hello World',
3941
msg1: 'Hello Vue'
42+
// 嵌套,调试 childOb.dep.depend() 代码涉及流程
43+
// arr: [
44+
// 10,
45+
// {
46+
// name: 'kai'
47+
// },
48+
// // () => console.log('i am arrow fn!'),
49+
// function () { console.log('i am common fn') },
50+
// 30
51+
// ]
4052
}
4153
},
4254
methods: {

Diff for: examples/00-vue-analysis/15-setter.html

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
// 总结一下派发更新的过程:
2222
// 1.当数据发生变化的时候,触发 defineReactive setter 逻辑(路径:src\core\observer\index.js),然后触发 dep.notify() 方法,
2323
// 把在依赖收集过程中订阅的所有观察者,也就是 watcher,都触发它们的 update 过程。
24-
// 该过程如果是异步(即 sync 为 false)的话,这个过程又利用了队列做了进一步优化,具体体现为调用 queueWatcher 方法进行处理(路径:src\core\observer\scheduler.js),
24+
// 该过程如果是异步(即 sync 为 false)的话,则这个过程又利用了队列做了进一步优化,具体体现为调用 queueWatcher 方法进行处理(路径:src\core\observer\scheduler.js),
2525
// 最后调用 nextTick(flushSchedulerQueue) 在下一次 tick 时执行所有 watcher 的 run 方法,
26-
// 在该方法中,如果被处理的是渲染 Watcher,则会重新执行 src\core\instance\lifecycle.js 文件中的“vm._update(vm._render(), hydrating)”代码,然后完成页面内容的重新渲染
26+
// 在该 run 方法中,如果被处理的是渲染 Watcher,则会重新执行 src\core\instance\lifecycle.js 文件中的“vm._update(vm._render(), hydrating)”代码,然后完成页面内容的重新渲染,
27+
// 当然了,重新渲染的过程又会进行依赖收集的,因为需要访问 getter 处理过下边用户传入的属性
2728
// 2.派发更新涉及代码调用流程:
2829
// defineReactive setter -> dep.notify -> watcher.update -> queueWatcher
2930
// -> nextTick(flushSchedulerQueue) -> flushSchedulerQueue

Diff for: examples/00-vue-analysis/17-reactive-warning.html

+20-5
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@
77
</head>
88
<body>
99
<div id="app">
10+
<!--
11+
msg是一个plain object,直接在模板中使用,访问 msg 会触发依赖收集,同时,
12+
又会使用 src\shared\util.js 中的 toString 方法将该 msg 对象转成字符串,
13+
此时又会进行一次依赖收集
14+
-->
1015
<h3>{{ msg }}</h3>
1116
<ul>
12-
<li v-for="item in items">{{ item }}</li>
17+
<!-- <li v-for="item in items">{{ item }}</li> -->
1318
</ul>
1419
<button @click="add">add</button>
1520
<button @click="change">change</button>
@@ -20,23 +25,33 @@ <h3>{{ msg }}</h3>
2025
// 1.Vue.set/delete 函数的定义位置:src\core\global-api\index.js,执行的set/delete方法在 src\core\observer\index.js 文件中
2126
// 2.响应式数据中对于对象新增删除属性,以及数组下标访问修改和添加数据等的变化是观测不到的,即无法触发 defineReactive 中的 setter 函数
2227
// 3.通过 Vue.set 以及数组的 API 可以解决这些问题,本质上是他们内部手动去做了依赖更新的派发(ob.dep.notify)
28+
// 4.为了便于理解,例子将 msg 和 items 涉及的逻辑分别注释掉进行分析:
29+
// (1) 首先是 msg 的例子,按照源码执行结果,可知:
30+
// { msg: { a: 'hello' } } 这个对象有个Observer实例,该实例上对应的 dep.id 为 2,
31+
// msg 属性对应的 dep.id 为 3,dep实例在 defineReactive中创建,然后放到了闭包中,
32+
// { a: 'hello' } 这个对象有个Observer实例,该实例上对应的 dep.id 为 4,
33+
// a 属性对应的 dep.id 为 5,dep实例在 defineReactive 中创建,然后放到了闭包中
34+
// 当使用 Vue.set 给 msg 对象动态添加 b 属性且能够实现页面重新渲染,是因为 defineReactive getter 中有“childOb.dep.depend()”这样的代码,
35+
// 对应上边就是 { a: 'hello' } 这个对象上 __ob__.dep.depend 方法,getter依赖收集时,也将当前watcher放到 { a: 'hello' } 对象 __ob__.dep 依赖收集器中了
36+
// 之后 Vue.set 方法中的代码“ob.dep.notify()”就能通知到 __ob__.dep 中的渲染Watcher,然后重新渲染页面了
37+
// (2) 然后是 items 的例子,
2338
new Vue({
2439
el: '#app',
2540
data () {
2641
return {
2742
msg: {
2843
a: 'hello'
2944
},
30-
items: [1, 2]
45+
// items: [1, 2]
3146
}
3247
},
3348
methods: {
34-
change () {n
49+
change () {
3550
// 错误写法
3651
// this.items[1] = 3
3752

3853
// 正确写法
39-
Vue.set(this.items, 1, 3)
54+
// Vue.set(this.items, 1, 3)
4055
// this.items.splice(1, 1, 3)
4156
},
4257
add () {
@@ -50,7 +65,7 @@ <h3>{{ msg }}</h3>
5065
// this.items[2] = 4
5166

5267
// 正确写法
53-
this.items.push(4)
68+
// this.items.push(4)
5469
}
5570
}
5671
})

Diff for: src/core/observer/dep.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ let uid = 0
99
/**
1010
* A dep is an observable that can have multiple
1111
* directives subscribing to it.
12+
* Dep 可以理解为 依赖/订阅者的收集器
1213
*/
1314
export default class Dep {
1415
// 静态属性,watcher 对象
@@ -33,7 +34,7 @@ export default class Dep {
3334
remove(this.subs, sub)
3435
}
3536

36-
// 将观察对象和 watcher 建立依赖
37+
// 将依赖收集器和 watcher 建立关联,表示当前 watcher 依赖于当前这个 dep
3738
depend () {
3839
if (Dep.target) {
3940
// 如果 target 存在,则把 dep 对象添加到 watcher 的依赖中

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

+11-8
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ export class Observer {
4646
vmCount: number; // number of vms that have this object as root $data
4747

4848
constructor (value: any) {
49+
// 传入的 value 要么为 Object,要么是 Array
4950
this.value = value
5051
this.dep = new Dep()
5152
this.vmCount = 0
5253
// 将 Observer 实例挂载到观察对象 value 的 __ob__ 属性上
5354
/*
54-
将Observer实例绑定到data的__ob__属性上面去,之前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了,def方法定义可以参考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16
55+
将Observer实例绑定到value对象的__ob__属性上面去,之前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了,
56+
def方法定义可以参考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16
5557
*/
5658
def(value, '__ob__', this)
5759
// 数组的响应式处理
@@ -153,7 +155,8 @@ export function observe (value: any, asRootData: ?boolean): Observer | void {
153155
} else if (
154156
/*
155157
这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等情况。
156-
而且该对象在shouldConvert的时候才会进行Observer。这是一个标识位,避免重复对value进行Observer
158+
而且该对象在shouldObserve的时候才会进行Observer。
159+
这是一个标识位,避免重复对value进行Observer
157160
*/
158161
shouldObserve &&
159162
!isServerRendering() &&
@@ -184,7 +187,7 @@ export function defineReactive (
184187
customSetter?: ?Function,
185188
shallow?: boolean
186189
) {
187-
// 创建依赖对象实例,为当前属性 key 收集依赖(收集观察当前属性的 watchers
190+
// 创建依赖收集器对象实例 dep,为当前属性 key 收集依赖(收集观察当前属性的 watcher
188191
const dep = new Dep()
189192
// 获取 obj 对象的属性描述符
190193
const property = Object.getOwnPropertyDescriptor(obj, key)
@@ -213,16 +216,16 @@ export function defineReactive (
213216
// 如果预定义的 getter 存在,则 value 等于调用 getter 返回的值
214217
// 否则直接赋予属性值
215218
const value = getter ? getter.call(obj) : val
216-
// 如果存在当前依赖目标(即 watcher 对象),建立依赖
219+
// 如果当前存在依赖目标(即 watcher 对象),建立依赖
217220
if (Dep.target) {
218221
// 依赖收集,内部首先会将 dep 对象放到 watcher 对象集合中,然后会将 watcher 对象放到 dep 对象的 subs 数组中
219222
// depend 内部调用方法:Dep.target.addDep(this) -> dep.addSub(this)
220223
dep.depend()
221224
// 如果子观察目标存在,建立子对象的依赖关系
222-
/*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在本身闭包中的depend,另一个是子元素的depend*/
225+
/*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个dep中,一个是正在本身闭包中的dep(即上边的dep变量),另一个是子元素的dep(即下边的childOb.dep)*/
223226
if (childOb) {
224227
// 配合 Vue.set 方法使用
225-
// 如果该行代码被注释掉,则调用 Vue.set 方法动态给对象添加属性,派发更新,渲染watcher的 update 过程不会执行,页面不会被重新渲染
228+
// 如果该行代码被注释掉,则调用 Vue.set 方法动态给对象添加属性,派发更新,渲染watcher的 update 过程不会执行(因为该 watcher 没放到子元素的 dep 依赖收集器中),页面不会被重新渲染
226229
childOb.dep.depend()
227230
// 如果属性值是数组,则特殊处理收集数组对象依赖
228231
/*是数组则需要对每一个成员都进行依赖收集,如果数组的成员还是数组,则递归。*/
@@ -256,7 +259,7 @@ export function defineReactive (
256259
} else {
257260
val = newVal
258261
}
259-
// 如果 newVal 是对象,进行 observe 处理,并返回 子的 observe 对象
262+
// 如果 newVal 是对象,进行 observe 处理,并返回子的 observe 对象
260263
childOb = !shallow && observe(newVal)
261264
// 派发更新(发布更改通知)
262265
/*dep对象通知所有的观察者watcher*/
@@ -362,7 +365,7 @@ function dependArray (value: Array<any>) {
362365
/*通过对象上的观察者进行依赖收集*/
363366
e && e.__ob__ && e.__ob__.dep.depend()
364367
if (Array.isArray(e)) {
365-
/*当数组成员还是数组的时候地柜执行该方法继续深层依赖收集,直到是对象为止。*/
368+
/*当数组成员还是数组的时候,递归执行该方法继续深层依赖收集,直到是对象为止。*/
366369
dependArray(e)
367370
}
368371
}

Diff for: src/core/observer/watcher.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ let uid = 0
2424
* This is used for both the $watch() api and directives.
2525
*/
2626
/*如果没有flush掉,直接push到队列中即可*/
27+
// Watcher 可以理解为 依赖/订阅者
2728
export default class Watcher {
2829
vm: Component;
2930
expression: string;
@@ -170,7 +171,7 @@ export default class Watcher {
170171
* 同时将本次执行 get 方法传入的 dep 实例及其id保存到 deps 和 depIds 中,并清除 newDeps 和 newDepIds 中的数据
171172
*/
172173
cleanupDeps () {
173-
// 遍历 deps,移除对 dep.subs 数组中 Watcher 的订阅
174+
// 每次添加完新的订阅,会从 dep 中移除掉旧的订阅watcher,避免不必要的依赖watcher回调执行(详见例子:examples/00-vue-analysis/14-getter)
174175
let i = this.deps.length
175176
while (i--) {
176177
const dep = this.deps[i]

0 commit comments

Comments
 (0)