01.JS 的程序性
想要了解响应性,那么首先我们先了解什么叫做:JS 的程序性
举个例子
<script>
// 定义一个商品对象,包含价格和数量
let product = {
price: 10,
quantity: 2
}
// 总价格
let total = product.price * product.quantity;
// 第一次打印
console.log(`总价格:${total}`);
// 修改了商品的数量
product.quantity = 5;
// 第二次打印
console.log(`总价格:${total}`);
</script>
运行结果两次都是20 其实我们希望第二次结果为50
02.让你的程序变得更加“聪明”?
你为了让你的程序变得更加 "聪明" , 所以你开始想:”如果数据变化了,重新执行运算就好了“。
那么怎么去做呢?你进行了一个这样的初步设想:
1.创建一个函数 effect,在其内部封装 计算总价格的表达式
2.在第一次打印总价格之前,执行 effect 方法
3.在第二次打印总价格之前,执行 effect 方法
那么这样我们是不是就可以在第二次打印时,得到我们想要的 50 了呢?
所以据此,你得到了如下的代码:
<script>
// 定义一个商品对象,包含价格和数量
let product = {
price: 10,
quantity: 2
}
// 总价格
let total = 0;
// 计算总价格的匿名函数
+ let effect = () => {
+ total = product.price * product.quantity;
+ };
// 第一次打印
+ effect();
console.log(`总价格:${total}`); // 总价格:20
// 修改了商品的数量
product.quantity = 5;
// 第二次打印
+ effect();
console.log(`总价格:${total}`); // 总价格:50
</script>
03.vue 2 的响应性核心 API:Object.defineProperty
vue2 以Object.defineProperty作为响应性的核心 API ,该 API 可以监听:指定对象的指定属性的 getter 和 setter行为
该 API 接收三个参数:指定对象、指定属性、属性描述符对象
举个例子
<script>
// 定义一个商品对象,包含价格和数量
let quantity = 2
let product = {
price: 10,
quantity: quantity
}
// 总价格
let total = 0;
// 计算总价格的匿名函数
let effect = () => {
total = product.price * product.quantity;
console.log(`总价格:${total}`);
};
// 第一次打印
effect();
// 监听 product 的 quantity 的 setter
Object.defineProperty(product, 'quantity', {
// 监听 product.quantity = xx 的行为,在触发该行为时重新执行 effect
set(newVal) {
quantity = newVal
// 重新触发 effect
effect()
},
// 监听 product.quantity,在触发该行为时,以 quantity 变量的值作为 product.quantity 的属性值
get() {
return quantity
}
});
product.quantity = 5;
</script>
运行结果:
总价格:20
总价格:50
04.Object.defineProperty 在设计层的缺陷
在 vue 官网中存在这样的一段描述 :
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
举个例子
<template>
<div id="app">
<ul>
<li v-for="(val, key, index) in obj" :key="index">
{{ key }} - {{ val }}
</li>
</ul>
<button @click="addObjKey">为对象增加属性</button>
<hr />
<ul>
<li v-for="(item, index) in arr" :key="index">
{{ item }}
</li>
</ul>
<button @click="addArrItem">为数组添加元素</button>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
obj: {
name: '张三',
age: 30
},
arr: ['张三', '李四']
}
},
methods: {
addObjKey() {
this.obj.gender = '男'
console.log(this.obj) // 通过打印可以发现,obj 中存在 gender 属性,但是视图中并没有体现
},
addArrItem() {
this.arr[2] = '王五'
console.log(this.arr) // 通过打印可以发现,arr 中存在 王五,但是视图中并没有体现
}
}
}
</script>
在上面的例子中,我们呈现了 vue2 中响应性的限制:
- 当为 对象 新增一个没有在
data中声明的属性时,新增的属性 不是响应性 的 - 当为 数组 通过下标的形式新增一个元素时,新增的元素 不是响应性 的
那么为什么会这样呢?
想要搞明白这个原因,那就需要明白官网所说的 由于 JavaScript 的限制 指的是什么意思。
我们知道
vue 2是以Object.defineProperty作为核心API实现的响应性Object.defineProperty只可以监听 指定对象的指定属性的 getter 和 setter- 被监听了
getter和setter的属性,就被叫做 该属性具备了响应性
那么这就意味着:我们 必须要知道指定对象中存在该属性,才可以为该属性指定响应性。
但是 由于 JavaScript 的限制,我们没有办法监听到 指定对象新增了一个属性,所以新增的属性就没有办法通过 Object.defineProperty 来监听 getter 和 setter,所以 新增的属性将失去响应性
05.vue3的响应性核心 API:proxy
因为 Object.defineProperty 存在的问题,所以 vue3 中修改了这个核心 API,改为使用Proxy代理进行实现
举个例子
<script>
// 定义一个商品对象,包含价格和数量
let product = {
price: 10,
quantity: 2
}
// new Proxy 接收两个参数(被代理对象,handler 对象)。
const proxyProduct = new Proxy(product, {
set(target, key, newVal, receiver) {
// 为 target 附新值
target[key] = newVal
// 触发 effect 重新计算
effect()
return true
},
get(target, key, receiver) {
return target[key]
}
})
// 总价格
let total = 0;
// 计算总价格的匿名函数
let effect = () => {
total = proxyProduct.price * proxyProduct.quantity;
console.log(`总价格:${total}`);
};
// 第一次打印
effect();
proxyProduct.quantity = 5;
</script>
运行结果:
总价格:20
总价格:50