此页面由社区从英文翻译而来。了解更多并加入 MDN Web Docs 社区。

View in English Always switch to English

Proxy

基线
广泛可用

自 2016年9月 起,此特性已在主流浏览器中得到支持,可在大多数设备和浏览器版本中正常使用。

Proxy 对象允许你为另一个对象创建代理,该代理能够拦截并重新定义该对象的基本操作。

描述

Proxy 对象允许你创建一个可替代原始对象的对象,但该对象可能重定义获取、设置和定义属性等基础 Object 操作。代理对象常用于记录属性访问、验证、格式化或清理输入等场景。

创建 Proxy 需提供两个参数:

  • target:需要代理的原始对象
  • handler:定义哪些操作将被拦截以及如何重定义拦截操作的对象

例如,此段代码为 target 对象创建了代理:

js
const target = {
  message1: "大家",
  message2: "好",
};

const handler1 = {};

const proxy1 = new Proxy(target, handler1);

由于 handler 是空的,此代理的行为如同直接对源对象进行操作:

js
console.log(proxy1.message1); // 大家
console.log(proxy1.message2); // 好

要自定义代理,我们在 handler 对象中定义函数:

js
const target = {
  message1: "大家",
  message2: "好",
};

const handler2 = {
  get(target, prop, receiver) {
    return "你好世界";
  },
};

const proxy2 = new Proxy(target, handler2);

这里我们提供了一个 get() 处理器的实现,它会拦截对目标对象属性访问的尝试。

处理器函数有时被称为陷阱,大概是因为它们会捕获对目标对象的调用。上文 handler2 中的陷阱重新定义了所有属性访问器:

js
console.log(proxy2.message1); // 你好世界
console.log(proxy2.message2); // 你好世界

代理常与 Reflect 对象配合使用,该对象提供了一些与 Proxy 陷阱同名的方法。Reflect 方法通过调用对应的对象内部方法来实现反射语义。例如,若不希望重定义对象行为,可调用 Reflect.get

js
const target = {
  message1: "大家",
  message2: "好",
};

const handler3 = {
  get(target, prop, receiver) {
    if (prop === "message2") {
      return "你好世界";
    }
    return Reflect.get(...arguments);
  },
};

const proxy3 = new Proxy(target, handler3);

console.log(proxy3.message1); // 大家
console.log(proxy3.message2); // 你好世界

Reflect 方法仍通过对象内部方法与对象交互——若在代理上调用该方法,它不会“解除代理化”。若在代理陷阱中使用 Reflect 方法,且该方法调用再次被陷阱拦截,则可能引发无限递归。

术语

以下术语用于描述代理的功能特性。

handler

作为 Proxy 构造函数的第二个参数传递的对象。它包含定义代理行为的陷阱。

陷阱(trap)

定义对应对象内部方法行为的函数(类似于操作系统中的陷阱概念。)

目标(target)

代理虚拟化的对象。它通常作为代理的存储后端使用。关于对象不可扩展性或不可配置属性的不变性(保持不变的语义)将针对目标对象进行验证。

不变量

在实现自定义操作时保持不变的语义。如果陷阱实现违反了处理器的不变性,将抛出 TypeError 异常。

对象内部方法

对象是属性的集合。然而,该语言并未提供任何机制来直接操作对象中存储的数据——相反,对象定义了一些内部方法来规定其交互方式。例如,当你读取 obj.x 时,你可能会期望发生以下情况:

  • x 属性会沿着原型链上行搜索,直至找到该属性。
  • x 是数据属性,则返回属性描述符的 value 属性。
  • x 是访问器属性,则调用获取器,并返回获取器的返回值。

这种过程在语言中并无特殊之处——仅仅是因为普通对象默认具有一个名为 [[Get]] 的内部方法,该方法即以这种行为方式定义。obj.x 属性访问语法只是调用了对象的 [[Get]] 方法,而对象会通过自身内部方法的实现来决定返回什么内容。

另一个例子是,数组与普通对象不同,因为它们具有一个神奇的 length 属性——当修改该属性时,系统会自动为数组分配空槽位或移除元素。同样地,向数组添加元素会自动改变 length 属性。这是因为数组拥有 [[DefineOwnProperty]] 内部方法,该方法在写入整数索引时会更新 length,在写入 length 值时则更新数组内容。这类内部方法实现与普通对象不同的特殊对象被称为特殊对象Proxy 使开发者能够全权定义自定义的特殊对象。

所有对象均具有以下内部方法:

内部方法 对应的陷阱
[[GetPrototypeOf]] getPrototypeOf()
[[SetPrototypeOf]] setPrototypeOf()
[[IsExtensible]] isExtensible()
[[PreventExtensions]] preventExtensions()
[[GetOwnProperty]] getOwnPropertyDescriptor()
[[DefineOwnProperty]] defineProperty()
[[HasProperty]] has()
[[Get]]