Vue2的不足

Vue2使用Object.defineProperty实现响应式,它有一些不足:

  • 对象只能劫持事先定义好的数据,新增的数据需要Vue.Set(xxx)

  • 数组只能使用七种(Vue重写后的)数组方法操作,使用下标修改某一项值就无法被劫持

    Object.defineProperty其实可以做到数组修改,但是会产生性能问题

reactive的实现

Vue3使用ES6的Proxy实现响应式。

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
import { track, trigger } from "./effect"

// 判断是否为对象
const isObject = (target:any)=> target!=null && typeof target == "object"


/**
* 实现对象的响应式
* @param target
* @returns
*/
export const reactive = <T extends object>(target: T): T=> {
return new Proxy(target, {
// 属性读取操作的捕捉器
// target:当前的对象 key:对象的属性 receiver:proxy或继承proxy的对象
get(target, key, receiver) {
// 有些情况下会造成上下文的错乱
// return target[key]
// 这样(有receiver)可以保证上下文的正确
let res = Reflect.get(target, key, receiver)
track(target, key)
// 递归监听嵌套对象
if(isObject(res)) {
return reactive(res as object)
}
return res
},
// 属性设置操作的捕捉器
set(target, key, value, receiver) {
// Reflect.set返回一个布尔值
let res = Reflect.set(target, key, value, receiver)
trigger(target, key)
return res
},
// delete操作符的捕捉器
// deleteProperty() {
// },
// 函数调用操作的捕捉器
// apply() {
// }
// 等,每种对象操作都有其捕捉器
})
}

effect

activeEffect收集副作用函数,初始化时也会调用一下,主要用来进行视图的改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 全局变量activeEffect
let activeEffect: Function

/**
* 收集当前副作用函数,并且初始化的时候调用一下
* @param fn 匿名函数
*/
export const effect = (fn: Function)=> {

// 一个闭包
const _effect = function() {
activeEffect = _effect
fn()
}

// 初始化的时候调用一下
_effect()
}

track

如图所示,通过以WeakMap为主的数据结构收集依赖。

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
// 需要把对象和对象的key拼装成这种数据结构
const targetMap = new WeakMap()

/**
* 依赖的收集
* @param target 响应式对象
* @param key
*/
export const track = <T extends object> (target: T, key: string | symbol)=> {

let depsMap = targetMap.get(target)
// 如果targetMap中没有这个对象key
if(!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}

let deps = depsMap.get(key)
// 同理,如果deps中没有这个对象值key
if(!deps) {
deps = new Set()
depsMap.set(key, deps)
}

deps.add(activeEffect)
}

trigger

更新依赖(就是调用这个值的副作用函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 依赖的更新
* @param target
* @param key
*/
export const trigger = <T extends object> (target: T, key: string | symbol) => {
const depsMap = targetMap.get(target)
if(!depsMap) {
throw "111"
}
const deps = depsMap.get(key)
if(!deps) {
throw "222"
}

deps.forEach((effect: Function)=> effect())
}

测试代码

要先把TypeScript代码编译成JavaScript代码。

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<div id="app"></div>

<script type="module">
import { reactive } from "./reactive.js"
import { effect } from "./effect.js"

// 定义一个reactive对象
const person = reactive({
name: "yajue",
age: 24,
// 对象嵌套的情况
foo: {
bar: {
baz: 1919810
}
}
})

// 接收匿名函数,模拟DOM和数据的绑定
effect(()=> {
document.querySelector("#app").innerText = `${person.name} - ${person.age} - ${person.foo.bar.baz}`
})

// 实现了当数据变化时,视图就变化的响应式效果
setTimeout(() => {
person.name = "yjsp"
setTimeout(() => {
person.age = 114514
setTimeout(() => {
person.foo.bar.baz = 364364
}, 1000)
}, 1000)
}, 2000)
</script>
</body>
</html>