一文教你深入理解 JavaScript 反射与代理
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 18 天,点击查看活动详情。
介绍
在最新的 ES6+当中,JavaScript 引入了一个两个新的概念反射和代理,他们为开发者提供了拦截并向基本操作嵌入额外行为的能力。具体地说,可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
Proxy 对象的类型
/// <reference no-default-lib="true"/>
// 代理处理器的类型
interface ProxyHandler<T extends object> {
apply?(target: T, thisArg: any, argArray: any[]): any;
construct?(target: T, argArray: any[], newTarget: Function): object;
defineProperty?(
target: T,
p: string | symbol,
attributes: PropertyDescriptor
): boolean;
deleteProperty?(target: T, p: string | symbol): boolean;
get?(target: T, p: string | symbol, receiver: any): any;
getOwnPropertyDescriptor?(
target: T,
p: string | symbol
): PropertyDescriptor | undefined;
getPrototypeOf?(target: T): object | null;
has?(target: T, p: string | symbol): boolean;
isExtensible?(target: T): boolean;
ownKeys?(target: T): ArrayLike<string | symbol>;
preventExtensions?(target: T): boolean;
set?(target: T, p: string | symbol, value: any, receiver: any): boolean;
setPrototypeOf?(target: T, v: object | null): boolean;
}
// Proxy 对象的构造函数类型接口
interface ProxyConstructor {
/**
*revocable创建的是可撤销的代理,这个代理可以通过返回的函数revoke来撤销代理
* @param target 代理对象
* @param handler 代理对象处理器
* @returns 返回一个对象,对象包含代理对象和一个撤销函数
*/
revocable<T extends object>(
target: T,
handler: ProxyHandler<T>
): { proxy: T; revoke: () => void };
/**
* 通过接口规范了我们的Proxy当中需要一个构造函数,这个构造函数接收两个参数,一个是代理对象,
* 一个是代理对象的处理器,最后这个构造函数返回一个代理对象
* */
new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}
//定义宏变量Proxy,类型是ProxyConstructor
declare var Proxy: ProxyConstructor;
创建空代理
const targetProperty = {
age: 18,
};
// 代理处理器为空
const handle = {};
//创建空代理
const targetProxy = new Proxy(targetProperty, handle);
//age属性会访问同一个值
console.log(targetProperty.age); //18
console.log(targetProxy.age); //18
//当给代理属性赋值,原始属性也会改变
targetProxy.age = 20;
console.log(targetProperty.age);
//但是当使用property来获取原型对象的时候却不一样
const target = Object.create(targetProperty);
console.log("target" + Object.getPrototypeOf(target));
console.log(Object.getPrototypeOf(targetProxy));
定义捕获器
get 捕获器
//定义捕获器
const target = {
age: 28,
};
//设置代理对象处理器
const handle = {
/**
* 获取属性值的拦截器
* @param {*} target 原始对象
* @param {*} property 属性
* @param {*} receiver 代理对象
*/
get(target, property, receiver) {
console.log(target, property, receiver);
},
};
// 获取代理
const targetProxy = new Proxy(target, handle);
//获取代理属性
console.log(targetProxy.age); //underfind
捕获器参数和反射 API
上面我们实现了一个get拦截器,当我们获取代理属性的时候,会调用这个拦截器,然后如果我们不返回值,那么我们就无法获取到代理属性对应的值。我们可以通过return target[property]来获取到代理属性对应的值。但是当我们需要处理一些比较复杂的逻辑的时候,这样自己来返回就显得有点麻烦,还容易出现很多 BUG。所以我们一般使用Reflect来进行反射。
//定义捕获器
const target = {
age: 28,
};
//设置代理对象处理器
const handle = {
/**
* 获取属性值的拦截器
* @param {*} target 原始对象
* @param {*} property 属性
* @param {*} receiver 代理对象
*/
get(target, property, receiver) {
console.log(target, property, receiver);
//调用反射对象,传入目标对象和属性名称
return Reflect.get(...arguments);
},
};
// 获取代理
const targetProxy = new Proxy(target, handle);
//获取代理属性
console.log(targetProxy.age); //underfind