一文教你深入理解 JavaScript 反射与代理

390 阅读3分钟

一文教你深入理解 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