vue2.x的响应式
- 实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
- 存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
- 存在问题:
-
Vue3.0的响应式
- 实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
- Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
- Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } })
-
5.reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
<!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>
<!--
5、reactive对比ref
从定义数据角度对比:
- ref用来定义:基本类型数据
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。
动员力角度对比:
- ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)
- reactive通过使用Proxy来实现响应(数据劫持),并通过Reflect操作源对象内部的数据
从使用角度对比:
- ref定义的数据:操作数据需要.value,读取数据是模板中直接读取不需要.value
- reactive定义的数据:操作与读取数据,均不需要.value
-->
<script>
let person = {
name: '张三',
age: 18
}
//模拟vue2中的响应式
//#region
// let p = {};
// Object.defineProperty(p, 'name', {
// configurable: true,
// get() { //有人读取name时调用
// return person.name
// },
// set(value) {//有人修改name时调用
// console.log('有人修改了name属性 ,我发现了,我要去更新界面了');
// person.name = value
// }
// })
// Object.defineProperty(p, 'age', {
// get() {//有人读取age时调用
// return person.age
// },
// set(value) {//有人修改时age时调用
// console.log('有人修改了age属性 ,我发现了,我要去更新界面了');
// person.age = value
// }
// })
//#endregion
//模拟vue3中的响应式
// console.log(window.Proxy);
//#region
const p = new Proxy(person, {
// 有人读取p的某个属性调用
get(target, proName) {
console.log(`有人读取了p身上的${proName}属性`);
// return target[proName];//真正的修改源数据
return Reflect.get(target, proName) // 真正的实现响应式
},
// 有人修改p的某个属性时或给p追加某个属性时调用
set(target, proName, value) {
console.log(`有人修改了p身上的${target}属性,我要去更新界面了!`);
// target[proName] = value//真正的修改源数据
return Reflect.set(target, proName, value) // 真正的实现响应式
},
// 有人删除p的某个属性时调用
deleteProperty(target, proName) {
console.log(`有人删除了p身上的${target}属性,我要去更新界面了!`);
// return delete target[proName]//真正的修改源数据
return Reflect.set(target, proName) // 真正的实现响应式
}
});
//#endregion
let obj = { a: 1, b: 2 };
// 通过Object.defineProperty去操作
//#region
// 报错不能重复定义
// try {
// // 使用Object.defineProperty,不健壮,一旦抛出错误,整个程序都跑不起来了
// // 所以需要配合try catch使用捕获异常
// Object.defineProperty(obj, 'c', {
// get() {
// return 3;
// }
// })
// Object.defineProperty(obj, 'c', {
// get() {
// return 4;
// }
// })
// } catch (error) {
// console.log(error);
// }
//#endregion
// 通过Reflect.defineProperty去操作 Reflect -> 反射对象,就不用写try catch
//#region
// const x1 = Reflect.defineProperty(obj, 'c', {
// get() {
// return 3
// }
// })
// console.log(x1);
// const x2 = Reflect.defineProperty(obj, 'c', {
// get() {
// return 4
// }
// })
// if (x2) {
// console.log('某某某某修改成功!');
// } else {
// console.log('某某某某修改失败!');
// }
// console.log(x2);
//#endregion
// console.log('@@@');
</script>
</body>
</html>
总结一下:
- 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等等。
- 通过Reflect(反射):对源对象的属性进行操作。
使用Reflect的原因:
- 使用Object.defineProperty,不健壮,一旦抛出错误,整个程序都跑不起来了, 所以需要配合try catch使用捕获异常
- 通过Reflect.defineProperty去操作 Reflect -> 反射对象,就不用写try catch