背景
上周分享了对象属性的两种类型,vue2通过相关定义实现了数据劫持,但是这个实现方式有一定的缺陷,比如他无法监听数组的变化,在比如他只能劫持对象的属性,因此实现的时候需要对每个对象的每个属性进行遍历。因此vue3的时候就换了实现方式 Proxy和Reflect。
正文
代理和反射是ES6增加的内容,它为我们提供了拦截并向基本操纵嵌入额外行为的能力。他可以用作目标对象的替身。看概念可能有些抽象,我们可以先实现一个简单的代理。
定义一个简单的代理
ES6给我们提供了Proxy构造函数让我们来创建代理。这个构造函数接受两个必填参数:目标对象和处理程序对象。
new Proxy(target, handler);
/**
* @param target 目标对象;
* @param handler 处理程序对象;
*/
// 目标对象
const target = {
id: 'target'
};
// 代理对象
const proxy = new Proxy(target, {});
// 这样我们就创建了一个简单的代理.在代理对象上执行任何的操作实际上都会应用到目标对象。
// id 属性会访问同一个值
console.log(target.id); // target
console.log(proxy.id); // target
// 给代理属性赋值会反映在两个对象上
// 因为这个赋值会转移到目标对象
proxy.id = 'bar';
console.log(target.id); // bar
console.log(proxy.id); // bar
// 严格相等可以用来区分代理和目标
console.log(target === proxy); // false
定义捕获器(基本操作的拦截器)
使用代理的主要目的就是可以定义捕获器。我们可以在handler对象里设置0或多个捕获器对对象的各种行为进行捕获拦截;
例如可以定义一个get()捕获器
const target = {
foo: 'bar',
id: 'target',
};
const handler = {
// 捕获器在处理程序对象中以方法名为键
get(trapTarget, property, receiver) {
if(property === 'id') {
return undefined;
}
return trapTarget[property];
}
};
const proxy = new Proxy(target, handler);
当通过代理对象执行get()操作时,就会触发定义的get()捕获器(proxy[property]、proxy.property等)进行拦截
console.log(proxy.foo); // bar
console.log(proxy.id); // undefined
get()捕获器可以接收三个参数:目标对象,要查询的属性和代理对象。我们可以基于这三个参数重建原始操作。
但并非所有的捕获器行为都像get()这样简单,每次都要手写代码比较麻烦。ES6给我们提供了Reflect全局对象,通过调用Reflect对象上的同名方法来进行轻松重建。
get(trapTarget, property, receiver) {
if(property === 'id') {
return undefined;
}
return Reflect.get(...arguments)
}
其他捕获器:
- set()
支持拦截的操作:proxy[property] = value;proxy.property = value等; - has()
支持拦截的操作:property in proxy等; - defineProperty()
支持拦截的操作:Object.defineProperty(proxy, property, descriptor)等; - deleteProperty()
支持拦截的操作:delete proxy.property; delete proxy[property]等;
.
.
.
常用的使用场景
使用代理我么可以实现一些有用的编程模式。
- 跟踪属性访问:通过捕获get、set、has等操作,我们可以知道对象属性什么时候被访问,被查询。
- 隐藏属性: 可以对目标对象上的属性进行隐藏
const hiddenProperties = ['foo', 'bar'];
const targetObject = {
foo: 1,
bar: 2,
baz: 3
};
const proxy = new Proxy(targetObject, {
get(target, property) {
if (hiddenProperties.includes(property)) {
return undefined;
} else {
return Reflect.get(...arguments);
}
},
has(target, property) {
if (hiddenProperties.includes(property)) {
return false;
} else {
return Reflect.has(...arguments);
}
}
// get()
console.log(proxy.foo); // undefined
console.log(proxy.bar); // undefined
console.log(proxy.baz); // 3
// has()
console.log('foo' in proxy); // false
console.log('bar' in proxy); // false
console.log('baz' in proxy); // true
- 属性验证:因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:
const target = {
onlyNumbersGoHere: 0
};
const proxy = new Proxy(target, {
set(target, property, value) {
if (typeof value !== 'number') {
return false;
} else {
return Reflect.set(...arguments);
}
}
});
proxy.onlyNumbersGoHere = 1;
console.log(proxy.onlyNumbersGoHere); // 1
proxy.onlyNumbersGoHere = '2';
console.log(proxy.onlyNumbersGoHere); // 1