Vue3的响应式原理(Proxy)
海边的小溪鱼 2017-07-14 Vue3
vue3中的响应式原理、手写Vue3响应式
Vue3响应式实现原理:使用了 Proxy 代理对象, 和 Reflect 反射对象
Vue3 通过 ES6 的代理对象 Proxy 进行响应式,代理 data 对象里面所有的属性及数组,访问属性时触发 get(),改变属性值时触发 set(),然后发布消息给订阅者,重新渲染页面。
- 通过 Proxy (代理):拦截对象中任意属性的变化(对属性的增删改查都可以被监听得到)
- 作用:监听某个对象属性的变化(监听对属性的增删改查操作)
- 通过 Reflect (反射):对被代理对象的属性进行操作(对属性的增删改查操作都是用的它)
- 作用:操作源对象的数据(增删改查)
// 源对象
let person = { name: "张三", age: 18 }
// 格式:new Proxy("代理哪个对象", 配置对象)
const p = new Proxy(person, {
// 通过 Proxy 中的 get,拦截读取属性值(监听读取操作)
get(target, propName) {
console.log(target == person); // true
return Reflect.get(target, propName) // 通过 Reflect 读取属性值
},
// 通过 Proxy 中的 set,拦截修改或添加属性(监听增改操作)
set(target, propName, value) {
Reflect.set(target, propName, value) // 通过 Reflect 增改属性
},
// 通过 Proxy 中的 deleteProperty,拦截删除属性(监听删除操作)
deleteProperty(target, propName) {
return Reflect.deleteProperty(target, propName) // 通过 Reflect 删除属性
}
// target 参数:源对象
// propName 参数:监听对象的哪个属性
// value 参数:最新修改的值
})
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
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
# 具体实现(模拟 Vue3 响应式)
1. Proxy(第一个参数)
new Proxy() 构造函数:第一个参数(需要代理哪个对象)
作用:将 源对象 交给 Proxy 代理,得到一个被 Proxy 所代理的对象(引用对象)
// 源对象
let person = { name: "张三", age: 18 }
// 将 源对象(person) 交给 Proxy 代理
const p = new Proxy(person, {}) // 参数1:需要代理哪个对象,参数2:配置项
console.log(p); // Proxy {name: "张三", age: 18},此时 p 就是被 Proxy 所代理的对象(引用对象)
// 此时,当我们操作 person 源对象里面的属性时,p对象也会发生相应的变化;反之同理
person.name = "李四" // 修改 name 属性
person.sex = "男" // 添加 sex 属性
delete person.age // 删除 age 属性
console.log("我是源对象person", person);
console.log("我是代理对象p", p);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
注意:此时不是响应式数据,数据虽然发生变化,但页面还没更新(只是完成了代理的操作,数据的变化并没有被监听到)
想要实现响应式,还需要捕获数据的修改,此时用到第二个参数(配置项)
2. Proxy(第二个参数)
new Proxy() 构造函数:第二个参数(配置项,是一个对象)
(1). 通过配置项中的 get()、set()、deleteProperty() 来监听某个对象中任意属性的变化(属性的增删改查都能被监听到) (2). 通过 Reflect (反射) 来操作源对象的数据(对属性的,增、删、改、查,操作都是用的它)
let person = { name: "张三", age: 18 }
const p = new Proxy(person, {
// 1. get() 方法:读取属性时会调用
get(target, propName) {
console.log(`有人读取了 p 身上的 ${propName} 属性`);
// return target[propName]
return Reflect.get(target, propName) // 通过 Reflect(反射对象) 读取属性值
},
// 2. set() 方法:修改和添加属性时会调用
set(target, propName, value) {
console.log(`有人修改 p 身上的 ${propName} 属性的值,新值为${value}`);
// target[propName] = value
Reflect.set(target, propName, value) // 通过 Reflect(反射对象) 添加/修改属性值
},
// 3. deleteProperty() 方法:删除属性时会调用
deleteProperty(target, propName) {
console.log(`有人删除了 p 身上的 ${propName} 属性`)
// return delete target[propName]
return Reflect.deleteProperty(target, propName) // 通过 Reflect(反射对象) 删除属性值
}
// 参数说明:target(源对象person)、propName(被读取的属性)、value(最新修改的值)
})
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
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
# Reflect 对象(Object的继承者)
Reflect 对象 与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 Api
目前正在尝试将 Object 身上一些有用的 api 转移到 Reflect 身上(很可能会成为 Object 的继承者)
注意:相对于封装框架来说,用 Reflect 比较好,所以 Vue3 响应式对数据的操作是用了 Reflect
1. Reflect 可以操作对象
const obj = { name: "张三", age: 18 }
// Reflect.get(对象, "属性名"):可以读取某个对象身上的某个属性
console.log(Reflect.get(obj, "name")); // 张三
// Reflect.set(对象, "属性名", 值):可以修改某个对象身上的某个属性的值
Reflect.set(obj, "age", 20)
// Reflect.deleteProperty(对象, "属性名"):可以删除某个对象身上的某个属性
Reflect.deleteProperty(obj, "name")
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
2. Reflect 身上的 defineProperty(Reflect.defineProperty)
const person = { a: 1, b: 2 }
/* 1. 通过 Object.defineProperty
监视两个同样的属性会直接报错(后面的代码不能继续执行),需要 try catch 来解决(不能更好的捕获错误)
*/
try {
Object.defineProperty(person, "c", {
get() {
console.log("有人读取了c 属性");
return 3
},
})
Object.defineProperty(person, "c", {
get() {
console.log("有人读取了c 属性");
return 4
},
})
} catch (error) {
console.log(error);
}
/* 2. 通过 Reflect.defineProperty
监视两个同样的属性,不报错,且返回一个布尔值来表示成功/失败(可以做更多的操作)
*/
const c1 = Reflect.defineProperty(person, "c", {
get() {
console.log("有人读取了c 属性");
return 3
},
})
console.log(c1); // true
const c2 = Reflect.defineProperty(person, "c", {
get() {
console.log("有人读取了c 属性");
return 4
},
})
console.log(c2); // false
if (c2) {
console.log("某某某操作成功了");
} else {
console.log("某某某操作失败了");
}
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
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