代理模式

使用 Proxy 对象,可以更好地控制与某些对象的交互。 每当与对象交互时,代理对象都可以确定行为,例如当我们获取一个值或设置一个值时。

一般来说,代理是指别人的替身。 您将与联系的人的代理人交谈,而不是直接与该人交谈。 JavaScript 中也会发生同样的情况:我们将与 Proxy 对象交互,而不是直接与目标对象交互。


Proxy 代理

创建一个代表 John Doe 的 person 对象。

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

我们希望与代理对象进行交互,而不是直接与该对象交互。 在 JavaScript 中,可以通过创建一个新的 Proxy 实例来轻松地创建一个新的代理。

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {});

Proxy 的第一个参数是一个代表处理程序的对象。 在处理程序对象中,我们可以根据交互的类型定义特定的行为。
虽然有很多方法可以添加到代理处理程序中,但最常见的两个是 get 和 set:

  • get:在尝试访问属性时被调用
  • set:在尝试修改属性时被调用

实际上,最终会发生以下情况:

我们将与 personProxy 进行交互,而不是直接与 person 对象交互。

让我们向 personProxy 代理添加处理程序。 当尝试修改一个属性,从而调用代理上的 set 方法时,我们希望代理记录该属性的先前值和新值。
当试图访问一个属性,从而调用代理上的 get 方法时,我们希望代理记录一个更易读的句子,其中包含该属性的任何值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`${prop} 的值是 ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`将 ${prop}${obj[prop]} 更改为 ${value}`);
    obj[prop] = value;
  }
});

Perfect! 让我们看看当我们尝试修改或检索属性时会发生什么。

1
2
3

personProxy.name; //name 的值是 John Doe
personProxy.age = 43; //年龄从 42 更改为 43

在访问 name 属性时,Proxy 返回了一个更好听的句子:name 的值是 John Doe。

在修改 age 属性时,Proxy 返回了该属性的上一个值和新值:将年龄从 42 更改为 43。



代理可用于添加验证。 用户不应该能够将人的年龄更改为字符串值,或者给他一个空的名字。 或者,如果用户试图访问对象上不存在的属性,我们应该让用户知道。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(
        `嗯..目标对象上似乎不存在此属性`
      );
    } else {
      console.log(`${prop} 的值是 ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`抱歉,您只能传递年龄的数值。`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`您需要提供一个有效的 name。`);
    } else {
      console.log(`将 ${prop}${obj[prop]} 更改为 ${value}。`);
      obj[prop] = value;
    }
  }
});

让我们看看当我们试图传递错误值时会发生什么!

1
2
3
4

personProxy.nonExistentProperty; //嗯..这个属性似乎不存在
personProxy.age = "44"; //抱歉,您只能传递年龄的数值
personProxy.name = ""; //您需要提供一个有效的名称

Proxy 代理确保我们没有修改具有错误值的 person 对象,这有助于我们保持数据纯净!


Reflect 映射

JavaScript 提供了一个名为 的内置对象Reflect,它使我们在使用代理时更容易操作目标对象。

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。
这些方法与 proxy handlers 的方法相同。

Reflect 不是一个函数对象,因此它是不可构造的。

在上面,我们尝试通过使用括号表示法直接获取或设置值来修改和访问代理内目标对象的属性。
现在,我们可以使用 Reflect 对象。 Reflect 对象上的方法与处理程序对象上的方法同名。

可以通过 Reflect.get()Reflect.set() 访问或修改目标对象上的属性,而不是通过 obj[prop] 访问属性或通过 obj[prop] = value 设置属性。
这些方法接收与处理程序(handlers)对象上的方法相同的参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`${prop} 的值是 ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`将 ${prop}${obj[prop]} 更改为 ${value}`);
    Reflect.set(obj, prop, value);
  }
});

Perfect! 我们可以使用 Reflect 对象轻松访问和修改目标对象的属性。

1
2
3
4

personProxy.name; //name 的值是 John Doe
personProxy.age = 43; //年龄从 42 更改为 43
personProxy.name = "Jane Doe"; //将名字从 John Doe 更改为 Jane Doe

优缺点

Proxy 是一种强大的方式来添加对对象行为的控制。 代理可以有各种用例:它可以帮助验证、格式化、通知或调试。

过度使用 Proxy 对象或对每个处理程序方法调用执行繁重的操作很容易对应用程序的性能产生负面影响。

最好不要对性能关键代码使用 Proxy。


知识点

  • Proxy
  • Reflect