认识
一、认识
Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
二、语法
const p = new Proxy(target, handler)
- target: 要使用
Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) - handler: 捕获器。一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理
p
的行为。
三、用法
3.1 监听函数
监听函数调用
function foo(){
console.log('我是 foo 函数');
}
const proxyFoo = new Proxy(foo,{
apply(target,thisArg,argumentsList){
console.log(`${target.name} 调用`);
return Reflect.apply(target,thisArg,argumentsList);
}
});
proxyFoo(10,20,30);
监听构造函数
function Person(name,age){
this.name = name;
this.age = age;
}
const proxyPerson = new Proxy(Person,{
construct(target,argumentsList,thisArg){
console.log(`${target.name} 构造`);
return Reflect.construct(target,argumentsList,thisArg);
}
});
const f = new proxyPerson('柏拉图',23);
console.log(f)
3.2 监听对象
通过Object.defineProperty()
实现的监听仅仅是已经定义好的属性或者元素才可以的。后续添加的属性或者元素都不可以监听到。另外,**Object.defineProperty()**方法仅仅能够监听获取属性值和设置属性值两种操作而已,其他的删除属性、获取属性描述符、设置属性描述符、检测对象属性等其他对对象属性的复杂操作都监听不到。
通过Proxy
实现的监听是真正的监听,可以实现对新增属性或者元素的监听。而且通过Proxy
的13种捕获器可以实现对对象属性、数组元素复杂操作监听,比如说设置属性、获取属性、删除属性、获取属性描述符、设置属性描述符、获取原型、设置原型等。
const obj = {
a: 1,
b: 2,
c: {
d: {
e: 3
}
}
};
const proxyObj = new Proxy(obj,{
get(target,property,reciver){
console.log(`${property}获取值`);
return Reflect.get(target,property,reciver);
},
set(target,property,value,reciver){
console.log(`${property} 设置值`);
return Reflect.set(target,property,value,reciver);
},
has(target,property){
console.log(`${property} in 操作`);
return Reflect.has(target,property);
},
deleteProperty(target,property,descriptor){
console.log(`${property} delete 操作`);
return Reflect.deleteProperty(target,property);
},
});
proxyObj.name = '柏拉图'; // 触发 set 拦截器
console.log(proxyObj.name); // 触发 get 拦截器
proxyObj.name = '柏拉图修改'; // 触发 set 拦截器
console.log(proxyObj.name); // 触发 get 拦截器
console.log('name' in proxyObj); // 触发 has 拦截器
3.3 监听数组
通过Object.defineProperty()
实现的监听仅仅是已经定义好的属性或者元素才可以的。后续添加的属性或者元素都不可以监听到。另外,**Object.defineProperty()**方法仅仅能够监听获取属性值和设置属性值两种操作而已,其他的删除属性、获取属性描述符、设置属性描述符、检测对象属性等其他对对象属性的复杂操作都监听不到。
通过Proxy
实现的监听是真正的监听,可以实现对新增属性或者元素的监听。而且通过Proxy
的13种捕获器可以实现对对象属性、数组元素复杂操作监听,比如说设置属性、获取属性、删除属性、获取属性描述符、设置属性描述符、获取原型、设置原型等。
const array = [];
const proxyArray = new Proxy(array,{
get(target,property,reciver){
console.log(`${property} 获取值`);
return Reflect.get(target,property,reciver);
},
set(target,property,value,reciver){
console.log(`${property} 设置值`);
return Reflect.set(target,property,value,reciver);
},
has(target,property){
console.log(`${property} in 操作`);
return Reflect.has(target,property);
},
has(target,property){
console.log(`${property} in 操作`);
return Reflect.has(target,property);
},
deleteProperty(target,property,descriptor){
console.log(`${property} delete 操作`);
return Reflect.deleteProperty(target,property);
},
});
proxyArray[0] = 10; // 触发 set 拦截器
console.log(proxyArray[0]); // 触发 get 拦截器
console.log(0 in proxyArray); // 触发 has 拦截器
四、精益求精
4.1 Proxy 的局限性?
Proxy
的局限性如下:
-
Proxy
只可以代理引用类型数据 -
Proxy
代理对象解构之后的属性将不会触发handler
4.2 为什么 Proxy 一定要配合 Reflect 使用?
Proxy
拦截器中的 receiver
表示 Proxy
或者继承Proxy
的对象(表示代理对象本身或者继承与代理对象的对象)。在 Proxy
中配合 Reflect
使用, 可以正确的为拦截器传递上下文引用。
针对普通属性: 通过代理对象访问普通属性, 可以正确传递上下文引用, 普通属性中的 this
指向代理对象,这样是正确的指向。
const source = {
_a: '嘻嘻',
a() {
console.log(this); // this 指向 Proxy 代理对象
return this._a;
}
};
const proxy = new Proxy(source, {
get(target, key, receiver) {
return target[key];
},
set(target, key, value, receiver) {
target[key] = value;
return true;
}
});
console.log(proxy.a());
针对访问器属性: 通过代理对象访问访问器属性, 访问器属性中的 this
指向的是源对象, 而不是指向代理对象,这样是错误的指向。
const source = {
_a: '嘻嘻',
get a() {
console.log(this); // this 指向 源对象
return this._a;
}
};
const proxy = new Proxy(source, {
get(target, key, receiver) {
return target[key];
},
set(target, key, value, receiver) {
target[key] = value;
return true;
}
});
console.log(proxy.a);
由上所述,我们需要解决访问器属性中 this
上下文传递错误的情况, 需要配合 Reflect
使用, 解决如下:
const source = {
_a: '嘻嘻',
get a() {
console.log(this); // this 指向 Proxy 代理对象
return this._a;
}
};
const proxy = new Proxy(source, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
target[key] = value;
return true;
}
});
console.log(proxy.a);