vue3响应系统(一)

本章先模拟vue3实现一个功能最简单的数据响应式系统.

副作用函数

当函数的执行会直接或间接影响其他函数的执行, 我们就称这个函数为副作用函数. 副作用函数很容易产生, 如函数中修改了全局变量. 如下面的effect函数.

1
2
3
4
5
// 全局变量
let val = 1
function effect(){
val = 2 // 修改全局变量, 产生副作用
}

实现

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
// 存储副作用函数的桶
const bucket = new WeakMap()
// 用一个全局变量存储被注册的副作用函数
let activeEffect
// 原始数据
const data = { text: 'hello world'}
// 对原始数据的代理
const obj = new Proxy(data, {
// 拦截读取操作
get(target, key) {
if (!activeEffect) return target[key]
// 往存储桶中存数据
let depsMap = bucket.get(target)
if (!depsMap) {
bucket.set(target, (depsMap = new Map()))
}
let deps = depsMap.get(key)
if (!deps) {
depsMap.set(key, (deps = new Set()))
}
deps.add(activeEffect)

return target[key]
},
// 拦截设置操作
set(target, key, newVal) {
// 设置属性值
target[key] = newVal
// 从存储桶中取数据
const depsMap = bucket.get(target)
if(!depsMap) return
const effects = depsMap.get(key)
// 把副作用函数从桶里取出并执行
effects && effects.forEach((fn) => fn());
return true
}
})
// 副作用函数
function effect(fn) {
activeEffect = fn
// 执行副作用函数
fn()
}
// 执行副作用函数, 触发读取
effect(() => {
document.body.innerHTML = obj.text;
})
// 1秒后修改响应式数据
setTimeout(() => {
obj.text = '1秒后改变数据'
obj.text2 = '123'
}, 1000);

WeakMap 还是 Map ?

为了细化副作用函数的绑定, 我们用到了Map来存储对象的副作用函数. 格式为{target1: {key1: Set1, key2: Set2}, target2: {key1: Set1}}, 如下图所示:

副作用函数存储

其中Set为集合, 存放副作用函数.

Map与WeakMap的区别

1
2
3
4
5
6
7
8
9
const map = new Map()
const weakmap = new WeakMap()

; (function () {
const foo = { foo: 1 }
const bar = {bar: 2}
map.set(foo, 1)
weakmap.set(bar, 2)
})()

foo和bar分别作为Map和WeakMap的key. 当函数表达式执行结束, 对于对象foo来说, 它仍作为map的key被引用着, 因此垃圾回收器不会把它从内存中移除.

然而对于对象bar来说, 由于WeakMap的key是弱引用, 它不影响垃圾回收器的工作.所以一旦表达式执行完毕, 垃圾回收器就会把对象bar从内存中移除, 并且也无法获取weakmap的key值, 也无法通过weakmap取的对象bar.

总结: WeakMap对key是弱引用, 不影响垃圾回收器的工作. 一旦key被垃圾回收器回收, 那么对应的键和值就访问不到了. 所以WeakMap经常用于存储那些只有当可以所引用的对象存在时(没有被回收)才有价值的信息.

为什么是WeakMap

知道了上面Map与WeakMap的区别, 就不难理解. 当target被垃圾回收器回收后, 我们自然也不再需要继续执行对应target下的副作用函数. 所以这边用WeakMap来作为存储集合.