跳到主要内容

认识

2023年08月17日
柏拉文
越努力,越幸运

一、认识


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实现的监听是真正的监听,可以实现对新增属性或者元素的监听。而且通过Proxy13种捕获器可以实现对对象属性、数组元素复杂操作监听,比如说设置属性、获取属性、删除属性、获取属性描述符、设置属性描述符、获取原型、设置原型等。

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实现的监听是真正的监听,可以实现对新增属性或者元素的监听。而且通过Proxy13种捕获器可以实现对对象属性、数组元素复杂操作监听,比如说设置属性、获取属性、删除属性、获取属性描述符、设置属性描述符、获取原型、设置原型等。

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 的局限性如下:

  1. Proxy 只可以代理引用类型数据

  2. 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);