vue源码学习之旅-day4

  1. Watcher 类

目的: 订阅者的实现。

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
 /**
* vm: vdom
* render: 源代码叫expOrFn,其实是optiosn入参的render方法
* cb: 执行后的回调函数
*/
class Wathcer{
constructor(vm,render,cb){
this.vm = vm
/**
* 省略 expOrFn 判断expression(字符串表达式) 和 function 的相关逻辑。简化处理
*/
this.expOrFn = render
this.ob = ob

this.value = this.get()
}
get(){
// 简化处理,源码中使用了数组作为栈来存储
Dep.target = this // 打上标记,告知Observer类,是Wathcer触发的get!,让它收集我作为订阅者!
let value = this.vm._data[this.expOrFn] // 触发Observer类的Object.defineProperty.get 句柄
Dep.target = null // 清除标记
return value
}
updata(){
/**
* 省略:懒加载。同步。watcher队列处理等相关判断逻辑
*/
this.run()
}
run(){
/**
* 省略相关判断
* 本方法作用主要是获取旧值和新值,然后对比两者。不同则执行回调
*
*/
// 关键代码:
// 1. 源码debugger一步步看,可以发现此处get触发Observer类的Object.defineProperty.get 拦截句柄;
// 2. 继而触发dep.depend()
// 3. 接着触发Wathcer类的addDep方法(所以需要定义),把自身作为订阅者推入订阅者队列
let value = this.get()
// 新旧值比较
if(value !== this.value){
let oldV = value
// 赋值
this.value = value
// 执行回调
this.cb.call(this.vm,value,oldV)
}
}
addDep(dep){
// 省略相关代码

// 添加到订阅者队列,后续notify分发通知时,自然可以执行对应的render方法,从而更新数据
dep.addSub(this)
}
}

  1. Observer类,Dep类,Watcher类的关系梳理

初步通读这三个类的相关定义,暂时明确了它们的一些基本作用,记录下,方便后续查阅

  • Observer类,观察者。

      1. 主要通过递归遍历入参options,对每个key执行defineReactive添加__ob__属性
      1. 对每个key通过Object.defineProperty设置get/set方法
  • Dep 类, 发布订阅器。处理一对多,多对多的关系

      1. 内部实现addSub,removeSub 来添加/移除 订阅者
      1. 内部实现 notify 来分发通知
      1. 内部实现 depend 方法,用于收集依赖 Watcher
  • Watcher 类,订阅者

      1. 内部实现updata 方法,用于更新数据
      1. 内部实现addDep 方法,用于将自身作为 “订阅者” 推入 “订阅者队列”
      1. 内部实现 run 方法,主要是判断新旧值,并执行后续回调
  1. 小练习,实现vm.$watch
1
2
3
4
5
6
7
8
9
10
11
const vm = new Vue({
data:{
a:1,
b:2
}
})
vm.$watch('a',()=>{
console.log("观测a成功")
})

setTimeOut(()=>{ vm.a = 233 },2000)

具体实现分 Dep.js, Watcher.js, Observer.js 及 index.html

  • (文件名不区分大小写,为了便于阅读,还是取首字母大写)
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
// Dep.js
let uid = 0;// 全局变量
class Dep {
static target = null;
constructor() {
this.id = uid++

this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
// 略
}
notify() {
const subs = this.subs.slice()
subs.forEach(watcher => {
watcher.updata()
})
}
depend() {
// 此处Dep.target是个Watcher实例
if(Dep.target){
// 传入dep
Dep.target.addDep(this)
}
}
}

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
65
66
67
68
69
// Observer.js

// 属性劫持
function defineReactive(
obj,// 劫持的对象
key,// 劫持的key
val,// key对应的value
customSetter,// 暂时不知道作用的入参
shallow // 暂时不知道作用的入参
){
let dep = new Dep()
// 简易处理val
arguments.length === 2? val = obj[key] : ''
// 递归处理子属性
let childOb = observe(val)
// get/set的实现
Object.defineProperty(obj,key,{
enumerable:true,// 可枚举?
configurable:true,// 描述属性可删改?
get(){
// 看是否Watcher实例的get调用
if(Dep.target){
// 递归收集依赖
dep.depend()
if(childOb){
childOb.dep.depend()
}
}
return val
},
set(newV){
val = newV
// 分发通知
dep.notify()
}
})
}
// 判断入参是否有__ob__属性,返回Observer实例
function observe(value,asRootData){
// 简易处理
let ob = value.__ob__ || new Observer(value)
if(asRootData && ob){
ob.vmCount ++ // 统计组件实例被使用次数
}
return ob
}
// 观察者类
class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
this.vmCount = 0 // 统计组件实例被使用次数

// 简易判断是否对象或数组
const type = Object.prototype.toString.call(value).slice(8, -1)
if (type === 'Object') {
this.walk(value)
}
else if (type === 'Array') {
// 数组处理,暂时省略
}
}
walk(value) {
const keys = Object.keys(value)
keys.forEach(key => {
defineReactive(value, key)
})
}
}
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
// Watcher.js

class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm
this.expOrFn = expOrFn
this.cb = cb

// 关键代码
this.value = this.get()
}
get() {
// 打上标记
Dep.target =this
// vm 是标准的vue.options。其中data会被赋值给_data;
// 此处会触发Observer的get拦截句柄,并最终将自身作为订阅者加入队列
let value = this.vm._data[this.expOrFn]
// 移除标记
Dep.target = null
return value
}
updata() {
// 省略相关代码
this.run()
}
run() {
let value = this.get()
if (value !== this.value) {
const oldV = value
// 赋予新值
this.value = value
// 执行回调
this.cb.call(this.vm, value, oldV)
}
}
addDep(dep){
// 省略相关代码
dep.addSub(this)
}
}
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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>

<body>
<script src="./js/Dep.js"></script>
<script src="./js/Observer.js"></script>
<script src="./js/Watcher.js"></script>

<script>
class Vue {
constructor(options = {}) {
// 简易处理
this.$options = options
let data = this._data = this.$options.data
// 代理vm._data.a -> vm.a
Object.keys(data).forEach(key=>{
this.myProxy(key)
})
// 观测data
observe(data, this)
}
$watch(expOrFn, cb, options) {
new Watcher(this, expOrFn, cb)
}
myProxy(key){
var obj = this
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
return obj._data[key]
},
set(newV){
obj._data[key] = newV
}
})
}
}

// 示例
const vm = new Vue({
data:{
a:1,
b:2
}
})

vm.$watch('a',()=>{
console.log("正在观测a")
})

setTimeout(()=>{
vm.a = 233
},2000)
</script>
</body>

</html>