Skip to content

Commit 5ec4f5d

Browse files
committed
chore: 补充watch原理涉及代码注释说明
1 parent 2a3f030 commit 5ec4f5d

File tree

8 files changed

+154
-20
lines changed

8 files changed

+154
-20
lines changed

Diff for: .vscode/bookmarks.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@
244244
"path": "src/core/observer/watcher.js",
245245
"bookmarks": [
246246
{
247-
"line": 119,
247+
"line": 120,
248248
"column": 20,
249249
"label": "watcher被作为 Dep.target 的执行代码"
250250
}

Diff for: examples/00-vue-analysis/18-computed.html

+5-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ <h3>{{ name }}</h3>
2828
// 然后进入 name 属性的 computedGetter 函数中(路径:src\core\instance\state.js),
2929
// 进入该函数时,会执行 watcher.evaluate(),这里的 watcher 是computed watcher(id=1),
3030
// evaluate 执行过程中,会调用下边传入的 name 属性的函数体(即this.get()),此时Dep.target为 computed watcher,然后访问到 this.useless,进而进入到 useless 属性的 defineReactive getter 中,然后将当前 computed watcher 放入 dep.id = 5 的依赖收集器中,
31-
// 之后执行 watcher.depend(),此时Dep.target 是上边的渲染watcher,该 computed watcher 的 depend 方法执行过程中,会将该 computed watcher 上的 deps 遍历一遍,然后把渲染 watcher 放到这些 dep 依赖收集器中,
31+
// 之后执行 watcher.depend(),此时 Dep.target 是上边的渲染watcher,该 computed watcher 的 depend 方法执行过程中,会将该 computed watcher 上的 deps 遍历一遍,然后把渲染 watcher 放到这些 dep 依赖收集器中,
3232
// 当前例子是将渲染 watcher 放入 dep.id = 5 的依赖收集器中,即让渲染 watcher 依赖于 useless 这个属性,此时 dep.id = 5 的依赖收集器上有2个watcher实例了
3333
// 最后返回 computed watcher 的 value 属性('please click change')
3434

@@ -43,13 +43,16 @@ <h3>{{ name }}</h3>
4343
new Vue({
4444
el: '#app',
4545
data () {
46+
// this.useless = 0
4647
return {
4748
firstName: 'yi',
4849
lastName: 'kai',
4950
useless: 0
5051
}
5152
},
5253
computed: {
54+
// 计算属性 getter 中所依赖的变量必须是经过响应式处理的,否则会出现异常,
55+
// 例如将 useless 变成是未经过响应式处理的变量,然后在看看 name 的变化!
5356
name () {
5457
if (this.useless > 0) {
5558
return this.firstName + ',' + this.lastName
@@ -61,6 +64,7 @@ <h3>{{ name }}</h3>
6164
methods: {
6265
change () {
6366
this.useless++
67+
console.log('this.useless---', this.useless)
6468
},
6569
changeLastName () {
6670
this.lastName = 'jia'

Diff for: examples/00-vue-analysis/19-watch.html

+141-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,143 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="UTF-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6-
<title>19-watch</title>
7-
</head>
8-
<body>
9-
<h1>19-watch</h1>
10-
<script>
11-
// 示例参看 examples/vue-cli-vue2.6-project 工程!
12-
// 相关注释也在工程中注明了!
13-
14-
</script>
15-
</body>
16-
</html>
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>19-watch</title>
7+
</head>
8+
<body>
9+
<div id="app">
10+
<h3>{{ name }}</h3>
11+
<button @click="change">change</button>
12+
<button @click="changeLastName">changeLastName</button>
13+
</div>
14+
<script src="../../dist/vue.js"></script>
15+
<script>
16+
// 1.watch 处理过程涉及代码流程:
17+
// 如果是根 Vue 实例,则:initMixin -> initState -> initWatch(vm, opts.watch) -> createWatcher -> Vue.prototype.$watch
18+
// -> new Watcher(vm, expOrFn, cb, options)
19+
// 2.我们以下边的代码为例:
20+
// (1)首先,对于 data 属性返回的这个对象,在依赖收集阶段,有:
21+
// { firstName: 'yi', lastName: 'kai', useless: 0, nested: { a: { b: 1 } } } 这个对象有个Observer实例,该实例上对应的 dep.id 为 2
22+
// firstName 属性对应的 dep.id 为 3
23+
// lastName 属性对应的 dep.id 为 4
24+
// useless 属性对应的 dep.id 为 5
25+
// nested 属性对应的 dep.id 为 6
26+
// { a: { b: 1 } } 对象有个Observer实例,该实例上对应的 dep.id 为 7
27+
// nested.a 属性对应的 dep.id 为 8
28+
// { b: 1 } 对象有个Observer实例,该实例上对应的 dep.id 为 9
29+
// nested.a.b 属性对应的 dep.id 为 10
30+
31+
// (2)首先是user watcher 创建过程,因为先执行 initComputed,因此下边 name 属性对应的 computed watcher 的 id = 1,
32+
// a.然后执行 initWatch(vm, opts.watch),最终调用 Vue.prototype.$watch 创建了 useless user watcher(id=2), useless user watcher 创建过程中会执行 watcher.get 方法,
33+
// 会访问 useless,进而会进入 useless 的 defineReactive 中的 reactiveGetter 中,然后将当前 useless user watcher(id=2) 添加到 id=5 的 dep 依赖收集器中
34+
35+
// b.之后 initWatch 来到 name 属性,然后创建 name user watcher(id=3), 执行 watcher.get 方法后,会访问 name,会进入 name 的 computedGetter 方法中,
36+
// 然后执行 compted watcher 的 evaluate 方法,之后也会进入 useless 的 defineReactive 中的 getter 中,然后将当前 name computed watcher(id=1) 添加到 id=5 的 dep 依赖收集器中,
37+
// 然后 evaluate 执行结束,接着执行 name computed watcher.depend() 将当前 Dep.target(也就是 name user watcher) 也放到该 name computed watcher 对应的 id=5 的 dep 依赖收集器中,
38+
// 此时 id=5 的 dep 上有 id 为 2/1/3 的 watcher。接着回到 $watch 方法中,由于下边示例设置了 `immediate: true`,因此会执行 cb.call(vm, watcher.value), 也即下边 name user watcher 的 handler 方法会立即被执行(注意此时 render watcher 还没有创建)
39+
40+
// c.之后 initWatch 来到 nested 属性,然后创建 nested user watcher(id=4), 执行 watcher.get 方法后,会访问 nested,会进入 nested 的 reactiveGetter 中,
41+
// 然后将当前 nested user watcher(id=4) 添加到 id=6 的 dep 依赖收集器中, 然后因为 reactiveGetter 中 childOb 存在,接着会将当前 nested user watcher(id=4) 添加到 id=7 的 dep 依赖收集器中,
42+
// 回到 nested user watcher 的 get 方法中,由于下边示例设置了 `deep: true`,因此接着执行 traverse(value) -> _traverse(val, seenObjects) 方法,_traverse 方法执行过程中会先后读取到 defineReactive 处理过后的 a 和 b 属性,
43+
// 然后会先把当前 Dep.target(也就是 id=4 的 nested user watcher) 放到 nested.a 对应的 id=8 的 dep 依赖收集器中,然后因为 reactiveGetter 中 childOb 存在,接着会将当前 nested user watcher(id=4) 添加到 id=9 的 dep 依赖收集器中,
44+
// 回到 _traverse(val, seenObjects) 中,将上次返回的 { b: 1 } 对象作为实参传入下次递归调用的 _traverse 方法,然后进入 nested.a.b 的 reactiveGetter 中,将 nested user watcher 加入到 id=10 的 dep 依赖收集器中,
45+
// 然后退出 _traverse 递归,此时发现,nested user watcher(id=4) 会存放在 id=6/7/8/9/10 的 dep 依赖收集器中
46+
47+
// d.接着类似的,调用 Vue.prototype.$watch 创建了创建 lastName user watcher(id=5),进入 lastName 的 reactiveGetter 中将当前 Dep.target(也就是 id=5 的 lastName user watcher) 放到 id=4 的 dep 依赖收集器中,
48+
// 至此,1个 computed watcher 和 4个 user watcher 的创建过程结束。
49+
// 之后是 render watcher 的执行,执行过程会读取到 name 这个计算属性,进而读取到 useless 属性,最终会把 render watcher(id=6) 加入到 id=5 的 dep 依赖收集器(useless属性对应的 dep)中,
50+
// 即页面首次渲染成功后,id=5 的 dep 上有4个 watcher, id 分别为 2/1/3/6
51+
52+
// (3)然后是 user watcher 触发回调的场景:
53+
// a.当我点击 change 方法,将 useless 变成 > 0 的值时,会进入 useless 的 reactiveSetter 中,然后调用 dep.notify 派发更新,让依赖 useless 的4个 watcher 依次去执行 update 方法,
54+
// 除了 id=1 的 computed watcher 直接执行 `this.dirty = true` 外,其他 3个 watcher 都是调用 `queueWatcher(this)` 将自己放到异步队列中,然后在下次 tick 中被执行,即最终在 flushSchedulerQueue 方法(路径:src\core\observer\scheduler.js)中被执行;
55+
// 接着 change 方法中执行 `this.nested.a.b = 2`,先进入 nested 和 nested.a 的 reactiveGetter 中,不过由于 Dep.target 为 undefined,没什么操作,
56+
// 然后进入 nested.a.b 的 reactiveSetter 中,调用 dep.notify 派发更新,让依赖 nested.a.b 的 nested user watcher(id=4) 去执行 update 方法,由于该 user watcher 设置了 `sync: true`,因此会直接同步调用 `watcher.run` 方法,
57+
// 最终 nested user watcher 是最先调用回调的 user watcher,然后所有同步代码执行结束后,flushSchedulerQueue 方法会被执行,
58+
// 然后将上边加入 queue 中的 3 个 watcher 依次执行 run 方法:
59+
// a.1.先是执行 useless user watcher(id=2) 的 run 方法,因此控制台先输出该 user watcher 的回调执行的结果;
60+
// a.2.然后执行 name user watcher(id=3) 的 run 方法,由于 useless > 0 了,因此 name 计算属性函数体执行过程中会访问 firstName 和 lastName,因此这 2 个属性的 reactiveGetter 中会把 name computed watcher(id=1) 先后放到 id=3 和 id=4 的 dep 依赖收集器中,
61+
// 最终 name computed watcher 将 'yi,kai' 字符返回作为 name user watcher 回调的 newVal 并执行该回调;
62+
// a.3.最后执行的是 render watcher(id=6),即重新渲染页面内容
63+
64+
// computed 和 watch 总结下:
65+
// (1) 计算属性本质是 computed watcher
66+
// (2) 侦听属性的本质是 user watcher,它还支持 deep/sync/immediate 等配置
67+
// (3) 计算属性适合在模板渲染中,计算属性值是依赖了其他的响应对象甚至是其他计算属性计算而来的;
68+
// 而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑
69+
new Vue({
70+
el: "#app",
71+
data() {
72+
return {
73+
firstName: "yi",
74+
lastName: "kai",
75+
useless: 0,
76+
nested: {
77+
a: {
78+
b: 1,
79+
},
80+
},
81+
};
82+
},
83+
computed: {
84+
name() {
85+
if (this.useless > 0) {
86+
return this.firstName + "," + this.lastName;
87+
}
88+
89+
return "please click change";
90+
},
91+
},
92+
watch: {
93+
// 属性值是 Function
94+
useless(newVal) {
95+
console.log("useless: ", newVal);
96+
},
97+
// 属性值是 Object,immediate 为 true
98+
name: {
99+
immediate: true,
100+
handler(newVal) {
101+
console.log("name: ", newVal);
102+
},
103+
},
104+
// 属性值是 Object,deep 为 true
105+
nested: {
106+
deep: true,
107+
// 默认情况下,当监听的内容发生变化时,配置的这几个 user watcher 的 cb 会在下一次 tick 执行,即数据修改到 cb 函数执行是异步的
108+
// 如果想让数据变化后同步执行 cb 函数,可以设置 sync: true,也即可以提升 watch cb 函数执行的优先级
109+
sync: true,
110+
handler(newVal, oldVal) {
111+
// 当 change 方法中执行 `this.nested.a.b = 2` 后,加上这里设置了 `deep: true`,
112+
// 通过调试后发现,newVal 和 oldVal 本质上指向的是同一个内存地址,地址中的新值为 { a: { b: 2 } },
113+
// 由于 watcher.run 方法中还有 `isObject(value)` 的判断,因此这里的 handler 回调依然会被执行,尽管 newVal === oldVal
114+
// 输出结果:2 2 true
115+
console.log("nested: ", newVal.a.b, oldVal.a.b, newVal === oldVal);
116+
},
117+
},
118+
// 属性值是 string
119+
lastName: "onLastNameChange",
120+
// 属性值是 Array
121+
// lastName: [
122+
// 'onLastNameChange',
123+
// function onLastNameChange2 (newVal) {
124+
// console.log('onLastNameChange2: ', newVal)
125+
// }
126+
// ]
127+
},
128+
methods: {
129+
change() {
130+
this.useless++;
131+
this.nested.a.b = 2;
132+
},
133+
changeLastName() {
134+
this.lastName = "jia";
135+
},
136+
onLastNameChange(newVal) {
137+
console.log("onLastNameChange: ", newVal);
138+
},
139+
},
140+
});
141+
</script>
142+
</body>
143+
</html>

Diff for: examples/00-vue-analysis/20-props.html

Whitespace-only changes.

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

+2
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ function initMethods (vm: Component, methods: Object) {
345345
}
346346
}
347347

348+
// watch 的类型注解:{ [key: string]: string | Function | Object | Array }
348349
function initWatch (vm: Component, watch: Object) {
349350
for (const key in watch) {
350351
const handler = watch[key]
@@ -437,6 +438,7 @@ export function stateMixin (Vue: Class<Component>) {
437438
options?: Object
438439
): Function {
439440
const vm: Component = this
441+
// 可以直接在业务代码中使用 this.$watch 的,因此这里还是要对 cb 是纯对象的写法做下处理
440442
if (isPlainObject(cb)) {
441443
return createWatcher(vm, expOrFn, cb, options)
442444
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default class Dep {
3737
// 将依赖收集器和 watcher 建立关联,表示当前 watcher 依赖于当前这个 dep
3838
depend () {
3939
if (Dep.target) {
40-
// 如果 target 存在,则把 dep 对象添加到 watcher 的依赖中
40+
// 如果 target 存在,则把 dep 对象添加到 watcher 的依赖(newDepIds和newDeps)中
4141
Dep.target.addDep(this)
4242
}
4343
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export function defineReactive (
219219
// 如果当前存在依赖目标(即 watcher 对象),建立依赖
220220
if (Dep.target) {
221221
// 依赖收集,内部首先会将 dep 对象放到 watcher 对象集合中,然后会将 watcher 对象放到 dep 对象的 subs 数组中
222-
// depend 内部调用方法:Dep.target.addDep(this) -> dep.addSub(this)
222+
// depend 内部调用方法:Dep.target.addDep(this) -> dep.addSub(this),第一个 this 是 Dep 实例,第二个 this 是 Watcher 实例
223223
dep.depend()
224224
// 如果子观察目标存在,建立子对象的依赖关系
225225
/*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个dep中,一个是正在本身闭包中的dep(即上边的dep变量),另一个是子元素的dep(即下边的childOb.dep)*/
@@ -247,7 +247,7 @@ export function defineReactive (
247247
/* eslint-disable no-self-compare */
248248
if (newVal === value || (newVal !== newVal && value !== value)) {
249249
return
250-
}
250+
}
251251
/* eslint-enable no-self-compare */
252252
if (process.env.NODE_ENV !== 'production' && customSetter) {
253253
customSetter()

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export default class Watcher {
6363
// options
6464
if (options) {
6565
this.deep = !!options.deep
66+
// user 为 true,说明是 user watcher
6667
this.user = !!options.user
6768
// lazy 为 true,说明是 computed watcher
6869
this.lazy = !!options.lazy
@@ -91,7 +92,7 @@ export default class Watcher {
9192
this.getter = expOrFn
9293
} else {
9394
// expOrFn 是字符串的时候,例如 watch: { 'person.name': function ... }
94-
// parsePath('person.name') 会返回一个函数获取 person.name 的值
95+
// parsePath('person.name') 会返回一个获取 person.name 值的函数
9596
this.getter = parsePath(expOrFn)
9697
if (!this.getter) {
9798
this.getter = noop

0 commit comments

Comments
 (0)