Vue3 响应式实现:Proxy 与 defineProperty 对比
如下代码
我们创建了一个 effect 函数用来计算 total 的值,我们在每次打印 total 之前都调用了 effect 函数,所以当 product 变化时,我们能获取到 total 当时的值。 但是不能每一次 product 发生变化我们都手动去触发 effect 函数去获取最新的值吧,这样显得十分繁琐,如何去解决这个问题呢?
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script>
let product = {
price: 10,
quantity: 2,
};
let total = 0;
let effect = () => {
// 在 effect 计算时,我们获取了 product 中属性的值,这是 getter 行为
total = product.price * product.quantity;
};
effect();
console.log('总价格:' + total);
// 修改 quantity 属性,这是 setter 行为
product.quantity = 5;
effect();
console.log('总价格:' + total);
</script>
</body>
</html>
使用 Object.defineProperty 去解决
如下我们使用了 Object.defineProperty 对 product 的 quantity 属性进行了代理。当 quantity 发生变化的时候会触发 setter 并且更新 quantity 的值,然后执行 effect。 通过下面这种方法可以实现,每次修改 product 的 quantity 属性的值,都会自动触发 effect
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script>
let product = {
price: 10,
quantity: 2,
};
Object.defineProperty(product, 'quantity', {
set(newval) {
console.log('触发 setter', newval);
quantity = newval;
effect();
},
get() {
console.log('触发 getter');
return quantity;
},
});
let total = 0;
let effect = () => {
total = product.price * product.quantity;
};
console.log('总价格:' + total);
product.quantity = 5;
console.log('总价格:' + total);
</script>
</body>
</html>
Object.defineProperty 的问题
虽然 Object.defineProperty 可以通过 getter 与 setter 实现响应式,但是这样只能监听到数据的修改操作,当我们监听的对象有新增或者删除属性时 Object.defineProperty 并不能实现相应式变化。
这种问题在 vue2 中都是通过一些特殊的 hack 手段才能实现响应式,如对对象整体重新赋值,或者调用 vue 提供的特殊 api 去实现
vue3 解决响应式的方法
proxy
如下代码所示,我们使用 proxy 取代了 Object.defineProperty
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script>
let product = {
price: 10,
quantity: 2,
};
const proxyProduct = new Proxy(product, {
set(target, key, newval, recevier) {
console.log('触发 setter', target, key, newval);
target[key] = newval;
effect();
},
get(target, key, recevier) {
console.log('触发 getter', target, key);
return target[key];
},
});
let total = 0;
let effect = () => {
total = proxyProduct.price * proxyProduct.quantity;
};
console.log('总价格:' + total);
proxyProduct.quantity = 5;
console.log('总价格:' + total);
</script>
</body>
</html>
综上所述,可以看出 Object.defineProperty 与 proxy 区别如下:
proxy
proxy将代理一个对象(被代理对象),得到一个新的对象(代理对象),同时拥有被代理对象中所有的属性。- 当想要修改对象的指定属性时,我们应该使用代理对象进行修改
- 代理对象的任何一个属性都可以触发
handler的getter和setter
Object.defineProperty
Object.defineProperty为指定对象的指定属性设置属性描述符。- 当想要修改对象的指定属性时,可以使用原对象进行修改
- 通过属性描述符,只有被监听的指定属性, 才可以触发
getter和setter
所以当 vue3 通过 proxy 实现响应性 api 之后,vue 将不会在存在新增属性时失去响应性的问题。
使用 Reflect 配合 proxy 改变 this 指向问题
如下代码,在被代理对象内容通过 this 触发 getter 与 setter 时,需要使用 Reflect.get(target, key, recevier) 去获取,否则,当前的 this 对象依旧是被代理对象
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<script>
let p1 = {
firstName: 'w',
lastName: 'fly',
get fullName() {
return this.firstName + this.lastName;
},
};
const proxy = new Proxy(p1, {
get(target, key, recevier) {
console.log('触发 getter');
return target[key];
},
});
// 只会触发一次,这时 get 中的 this 指向 p1
console.log(proxy.fullName);
let p2 = {
firstName: 'w',
lastName: 'fly',
get fullName() {
return this.firstName + this.lastName;
},
};
const proxy2 = new Proxy(p1, {
get(target, key, recevier) {
console.log('触发 getter', recevier);
return Reflect.get(target, key, recevier);
},
});
// 会触发三次,这时 get 中的 this 指向 proxy2
console.log(proxy2.fullName);
</script>
</body>
</html>

还没有留言,来发表第一条吧~