Appearance
数据拦截的本质
数据拦截
- 对数据的读写操作中途进行打断,方便做一些额外的事情。
JS中的数据拦截
主要方式有两种:
- Object.defineProperty Vue2.x的响应式
- Proxy Vue3.x的响应式
Object.defineProperty
静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
Object.defineProperty(obj, prop, descriptor)
obj: 要定义属性的对象
prop: 一个字符串或 Symbol,指定了要定义或修改的属性键
descriptor: 要定义或修改的属性的描述符
属性的描述符是一个对象,主要有两种类型:
- 数据描述符
- 访问器描述符
数据描述符:
value:设置属性值,默认值为 undefined
writable:设置属性值是否可写,默认值为 false
访问器描述符:
get:用作属性 getter 的函数,如果没有 getter 则为 undefined
set:用作属性 setter 的函数,如果没有 setter 则为 undefined
enumerable:当且仅当该属性在对应对象的属性枚举中出现时,值为 true。默认值为 false
configurable:是否可以配置该属性,默认值为 false。
下面配置主要是针对这么一些点:
该属性的类型是否能在数据属性和访问器属性之间更改
该属性是否能删除
描述符的其他属性是否能被更改
数据描述符和访问器描述符默认是互斥的
let obj = {
name: '张三',
}
Object.defineProperty(obj, 'name',{
get(){
console.log('获取name属性值');
return '张三';
},
set(newVal){
console.log('设置name属性值');
console.log('新值为:', newVal);
}
})
obj.name; // 读取name属性值
obj.name = '李四'; // 设置name属性值 新值为: 李四Proxy
允许你创建一个可替代原始对象的对象,但该对象可能重定义获取、设置和定义属性等基础 Object 操作
代理对象常用于记录属性访问、验证、格式化或清理输入等场景。
new Proxy(target, handler)
target: 目标对象
handler: 是个对象,对代理执行操作时的行为的函数
返回值: 返回一个代理对象,之后对属性的读写都是针对代理对象来做的
const obj = { a: 1, b: 2 };
const proxyObj = new Proxy(obj,{
get(target, key) {
console.log(`读取属性 ${key}`);
return Reflect.get(target, key);
},
set(target, key, value) {
console.log(`设置属性 ${key} 为 ${value}`);
return Reflect.set(target, key, value);
}
})
console.log(proxyObj.a); // 读取属性 a
proxyObj.b = 3; // 设置属性 b 为 3共同点
- 都可以拦截读取操作
- 都可以拦截写入操作
- 都可以实现深度拦截
差异点
- 拦截的广度
Vue3的响应式从Object.defineProperty替换为Proxy。
之所以替换是因为两者拦截的时候,无论是拦截的目标还是拦截的行为都是不同的。
- Object.defineProperty 是针对特定的属性的读写操作进行拦截
- Proxy 是针对一整个对象进行多种拦截行为,包括读取、设置、删除、属性描述符的获取和设置、原型的查看、函数调用等行为能够进行拦截。
如果使用Object.defineProperty,一旦后期给对象新增属性,是无法进行拦截的,因为Object.defineProperty在设置拦截的时候是针对特定的属性,所以新增的属性无法拦截。
但是Proxy不一样,它是针对整个对象,就算后面新增属性也是能够拦截的。- 性能的区别
多数情况下,Proxy是高效的,但是不能完全断定Proxy就一定比Object.defineProperty效率高,具体要看场景。
如果只是想拦截特定的属性,那么可以Object.defineProperty的效率会高些。
但是针对特定的属性的场景较少,一般情况下都是针对对象进行操作的。