JS - Proxy 代理捕获器原则 & 代理撤销

ES6 新增的代理(Proxy)与反射(Reflect)为开发者提供了拦截并向基础操作嵌入额外行为的能力,具体地说,可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用,即在对目标对象进行各种操作之前,可以在代理对象中对这些操作加以控制

⚠️: 由于代理是一种新的基础性语言能力,很多转译程序都不能把代理行为转化为之前的 ECMAScript 代码,而代理的行为实际上无可替代,换句话说,代理只能在 百分百 支持他们的平台上使用

思维导图

代理基础

代理是目标对象的抽象,可以作为目标对象的替身,有完全独立于目标对象,目标对象可以直接被操作,也可以通过代理来操作,即代理对象傻姑娘执行的任何操作实际上都会应用到目标对象,唯一可感知的不同就是代码中操作的是代理对象;但是代理对象和目标对象访问的不是同一个地址

const target = {
  id: "id15",
};

const handlder = {};

const proxy = new Proxy(target, handlder);
// 代理对象和目标对象访问的属性是同名的时候,本质访问的就是同一个值,
// 给目标对象 或者代理对象 属性赋值也会反映到两个对象上
log(proxy.id === target.id); // log: true

target.id = "id_15";
log(proxy.id); // log: id_15
log(target.id); // log: id_15

proxy.id = "pro_id";
log(proxy.id); // log: pro_id
log(target.id); // log: pro_id

// Proxy.rototype 是 undefiend,所以不能使用 instanceof 操作符
log(target instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check

捕获器

使用代理的主要目的是可以自定义捕获器(trap)。捕获器就是在处理程序对象中定义的拦截器,对每个处理程序对象可以定义 0 个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上调用。每次在代理对象上调用这些基本操作时,代理可以这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为。

所有的捕获器都可以基于自己的参数重建原始操作,可以通过调用全局的 Reflect 对象上(封装了原始行为)的同名方法来轻松重建,即处理程序对象中所有可以捕获的方法都有对应的 Reflect API 方法,这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与拦截方法相同的行为

const proxy = new Proxy(
  { name: "jakequc" },
  {
    // 捕获器在处理程序对象中以方法名为键
    // proxy.property / proxy[property] 等获取属性的时候会出发 get 捕获器
    get(target, key, receiver) {
      // target 是代理的目标对象,key 获取时的 键(属性),receiver 是代理对象
      // log(target, key, receiver);
      return target[key] + "__";
    },
  }
);

log(proxy.name); // log: jakequc__

const reproxy = new Proxy(
  {
    age: 23,
  },
  {
    // 甚至可以写成 get: Reflect.get
    get() {
      // 每一个 Proxy 中的拦截器名字 在 Reflect 中都存在,因此可以在 Proxy 拦截器中使用 Reflect
      return Reflect.get(...arguments);
    },
  }
);

log(reproxy.age); // log: 23

如果想创建一个可以有所有捕获方法然后将每个方法转发给对应 Reflect API 的代理,可以将 Proxy 的第二个参数直接传递为 Reflect

const allReflectPro = new Proxy(
  {
    name: "kj",
  },
  Reflect
);

log(allReflectPro.name); // log: kj

捕获器不变式

使用捕获器几乎可以改变所有基本方法的行为,但是也有限制;虽然每个捕获的方法都知道目标对象上下文、捕获函数签名,而捕获处理程序的行为必须遵循“捕获器不变式”,防止捕获器定义出现过于反常行为;如目标对象有一个不可配置且不可写的数据属性,那么捕获器返回一个与该属性不同的值时,会抛出 TypeError

const target = {};
Object.defineProperty(target, "const_key", {
  configurable: false, // 不可删除
  writable: false, // 不可写入
  value: "love", // const_key 的 值为 love 字符串
});

const proxy = new Proxy(target, {
  get() {
    // 这里违背了 目标对象 const_key 不可写入(更改)的规则,因此在获取对应的值时会触发 TypeError
    return "update_love";
  },
});

log(proxy.const_key); // TypeError: 'get' on proxy: property 'const_key' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'love' but got 'update_love')

可撤销代理

使用 new Proxy(target, handler) 创建普通代理来说,这种联系会在代理对象的生命周期内一直持续存在,Proxy.revocable(target,handler) 这个方法返回 revoke 方法可以撤销代理操作,值得注意的是,撤销代理的操作是不可逆的且 revoke 方法是幂等的(即调用多少次结果都一样),撤销代理之后再调用代理会抛出 TypeError;

// 代理对象和撤销代理函数在调用 revocable 在实例化同时生成
const { proxy, revoke } = Proxy.revocable(
  {
    name: "kj",
  },
  Reflect
);

log(proxy.name); // log: kj

revoke();

// 撤销代理之后,再使用代理就会报 TypeError 错误
log(proxy.name); // TypeError: Cannot perform 'get' on a proxy that has been revoked

猜你喜欢

转载自juejin.im/post/7253437782333538362