Skip to content

代理 / Proxy

Proxy 可以给目标对象定义一个关联的代理对象,代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

目标对象既可以直接被操作,也可以通过代理对象进行操作,直接操作目标对象则会绕过代理对象所施加的行为。

Proxy 构造函数

Proxy 构造函数接收两个参数,第一个参数为要代理的目标对象(target),第二个参数为处理程序对象(handler)。

两个参数都是必传的,否则会抛出 TypeError 错误。

Proxy.prototype

Proxy.prototypeundefined,因此我们不能使用 instanceof 操作符判断一个对象是否为 Proxy

捕获器 / trap

捕获器是在处理程序对象中定义的“基本操作的拦截器”,每个处理程序对象可以包含 0 到多个捕获器。每次在代理对象上进行这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相对应的行为。

需要注意的是,只有在代理对象上进行这些操作,才会触发捕获器,直接在目标对象上进行操作是无法触发捕获器的。

捕获器参数

所有捕获器都可以访问相应的参数,如 get 捕获器会接收到目标对象,要查询的属性和代理对象 3 个参数。

javascript
const handler = {
  get(trapTarget, property, receiver) {},
};

撤销代理

使用 Proxy 构造函数创建的代理对象无法撤销。

所幸 Proxy 提供了 revocable 静态方法,该方法接收目标对象和处理程序对象 2 个参数。返回的对象包含 proxyrevoke 两个属性。

调用 revoke 函数后,proxy 和 target 的关联就撤销了,并且是不可逆的。再访问代理则会抛出 TypeError 错误。

javascript
const target = {
  id: 'target',
};

const handler = {
  get() {
    return 'hello';
  },
};

const { proxy, revoke } = Proxy.revocable(target, handler);

console.log(proxy.id); // -> hello
console.log(target.id); // -> target

revoke();

console.log(proxy.id); // -> TypeError

对比 Object.defineProperty

Vue3 中舍弃了 Object.defineProperty() 方法,改用了 Proxy 实现双向绑定。以下简称 defineProperty

  • Proxy 应用于对象,defineProperty 应用于属性,对象新增属性时需要应用 defineProperty,而 Proxy 不需要
  • Proxy 兼容性不如 definePropertydefineProperty 能兼容到 IE9。
  • Proxy 监听的范围更广,如直接通过数组索引去更新数组元素。
  • Proxy 必须通过代理对象去触发拦截与监听,defineProperty 能够通过原对象触发拦截与监听

参考资料

  • 《JavaScript 高级程序设计(第四版)》