1
1
<!DOCTYPE html>
2
2
< 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 >
0 commit comments