vue源码学习之旅-day3

part2 响应式原理

  1. Dep构造函数的实现

目的:

    1. 调用Dep.target(一个后续的Watcher)的addDep添加依赖方法。从而收集依赖
    1. 根据依赖精准的添加订阅者
    1. 分发通知给每个订阅者
  • Dep 发布-订阅器的作用:

      1. 提供add,remove等方法添加和取消订阅
      1. 提供notify方法,通知订阅者执行相关方法
      1. 提供depend 方法,收集依赖
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
let uid = 0;
class Dep {
static target = null;// 当前正在被观察的对象,空/Watcher
constructor() {
// 每个Dep实例都有一个唯一id
this.id = uid++
// 订阅者队列
this.subs = []
}
// 添加订阅者
addSub(sub) {// sub:Watcher
this.subs.push(sub)
}
// 移除订阅者
removeSub(sub) {
this.subs = this.subs.filter(item => {
return item.id !== sub.id
})
}
// 分发订阅
notify() {
const subs = this.subs.slice()
// 通知每个订阅者更新状态
subs.forEach(item => {
item.update()
})
}
// 收集依赖 ;不理解可以暂时跳过,后续看完Observer 和 Watcher再回来看
depend() {
// 这里Dep.target为当前正在被观察的对象
if (Dep.target) {
Dep.target.addDep(this)
}
}
}
// 示例
const dep = new Dep()
// 添加订阅者
dep.addSub({
id: "001",
update() {
console.log("通知001,更新状态")
}
})
dep.addSub({
id: "002",
update() {
console.log("通知002,更新状态")
}
})
dep.addSub({
id: "003",
update() {
console.log("通知003,更新状态")
}
})
dep.notify() // 打印3个输出日志
dep.removeSub({
id: "002",
update() {
console.log("通知002,更新状态")
}
})
dep.notify() // 打印1,3两个输出日志
  1. Observer 构造函数的实现-简化版,暂时只考虑对象

目的:处理被观察者对象,通过往它身上附加get/set来收集依赖和分发状态

  • Observer 观察者类的作用:
      1. 实例化一个Dep,记录入参value,定义一个vmCount用于统计被使用次数
      1. 向入参添加一个__ob__属性,同时赋值dep,value,vmCount
      1. 遍历入参的键,调用defineReactive 设置反应性属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Observer {
constructor(value) {
this.value = value
this.vmCount = 0 // 统计当前实例被使用的次数
this.dep = new Dep() // 实例化一个发表订阅器实例
// 往被观察对象身上添加一个__ob__属性,同时赋值dep,vmCount,value自身给它
def(value, __ob__, this)
// 遍历value所有“对象类型”的key,调用defineReactive方法
this.walk(value)
}
walk(obj) {
let keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}

  1. defineReactive 定义反应性属性方法的实现
  • 难点之一,最好下载源码后,断点阅读,如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div id="app">
<p>{{ arr }}</p>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el:"#app",
data:function (){
return {
arr:[1,2,3],
info:{
name:'tom',
type:{
sort:"俄罗斯蓝猫",
color:"blue"
}
}
}
},
})
</script>
  • defineReactive 方法
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
function defineReactive(
obj,// 当前操作的对象
key,// 对象的key
val,// 监听的数据
customSetter,// 暂时不知道作用的形参
shallow,// 暂时不知道作用的形参
){
var dep = new Dep()

/**
* 省略一系列边界判断,以及obj自身是否定义了getter/setter,有就执行它
* **/

// 对被观察对象执行定义的observer 方法。注意不是前面定义的Observer构造函数;
// 主要作用是返回 一个Observer构造函数中定义的__ob__属性
var childOb = !shallow && obsevrer(val)
// debugger // 在源码此处断点可以看到,val是被观察者,如vue.options 的data

// 在这里对obj 的属性进行依赖收集和分发通知
Object.defineProperty({
obj,
key,
get(){
/**
* getter之类的特殊处理,略
*/

// 查看当前是否有正在被观察的对象,有的话执行它的 addDep 方法
// 正在被观察的对象 -> 一个Watcher实例,后续定义
// 核心代码,通过这个Dep.target全局变量,实现往Dep实例的subs中添加订阅者。
// 即调用 new Dep().addSub()
if(Dep.target){
dep.depend() // 等价于 Dep.target.addDep(Dep实例)
// 递归这个对象,收集全部依赖
if(childOb){
childOb.dep.depend()
}
}
return value
},
set(newValue){
/**
* setter之类的特殊处理,略
*/

// 核心实现
value = newValue
dep.notify() // 通知订阅者更新状态
}
})
}