有兴趣阅读我们的下一本书吗?了解更多关于 使用 React 构建大型 JavaScript Web 应用程序

设计模式

代理模式

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


一般来说,代理是指代替某人的人。与其直接与那个人交谈,不如与代理人交谈,代理人将代表你想联系的人。JavaScript 中也是一样:与其直接与目标对象交互,不如与代理对象交互。


让我们创建一个 person 对象,代表 John Doe。

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 的第二个参数是一个代表处理程序的对象。在处理程序对象中,我们可以根据交互类型定义特定的行为。虽然你可以添加到 Proxy 处理程序中 许多方法,但最常见的两个是 getset

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

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

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

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

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
  },
});

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

index.js
1const person = {
2 name: "John Doe",
3 age: 42,
4 nationality: "American"
5};
6
7const personProxy = new Proxy(person, {
8 get: (obj, prop) => {
9 console.log(`The value of ${prop} is ${obj[prop]}`);
10 },
11 set: (obj, prop, value) => {
12 console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
13 obj[prop] = value;
14 return true;
15 }
16});
17
18personProxy.name;
19personProxy.age = 43;

访问 name 属性时,代理返回了一个听起来更好的句子:The value of name is John Doe

修改 age 属性时,代理返回了该属性的先前值和新值:Changed age from 42 to 43


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

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(
        `Hmm.. this property doesn't seem to exist on the target object`
      );
    } else {
      console.log(`The value of ${prop} is ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`Sorry, you can only pass numeric values for age.`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`You need to provide a valid name.`);
    } else {
      console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
      obj[prop] = value;
    }
  },
});

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

index.js
1const person = {
2 name: "John Doe",
3 age: 42,
4 nationality: "American"
5};
6
7const personProxy = new Proxy(person, {
8 get: (obj, prop) => {
9 if (!obj[prop]) {
10 console.log(`Hmm.. this property doesn't seem to exist`);
11 } else {
12 console.log(`The value of ${prop} is ${obj[prop]}`);
13 }
14 },
15 set: (obj, prop, value) => {
16 if (prop === "age" && typeof value !== "number") {
17 console.log(`Sorry, you can only pass numeric values for age.`);
18 } else if (prop === "name" && value.length < 2) {
19 console.log(`You need to provide a valid name.`);
20 } else {
21 console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
22 obj[prop] = value;
23 }
24 return true;
25 }
26});
27
28personProxy.nonExistentProperty;
29personProxy.age = "44";
30personProxy.name = "";

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


反射

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

之前,我们尝试通过代理直接获取或设置方括号表示法中的值来修改和访问目标对象上的属性。相反,我们可以使用 Reflect 对象。Reflect 对象上的方法与 handler 对象上的方法具有相同的名称。

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

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    Reflect.set(obj, prop, value);
  },
});

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

index.js
1const person = {
2 name: "John Doe",
3 age: 42,
4 nationality: "American"
5};
6
7const personProxy = new Proxy(person, {
8 get: (obj, prop) => {
9 console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
10 },
11 set: (obj, prop, value) => {
12 console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
13 return Reflect.set(obj, prop, value);
14 }
15});
16
17personProxy.name;
18personProxy.age = 43;
19personProxy.name = "Jane Doe";

权衡

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

过度使用 Proxy 对象或在每次 handler 方法调用时执行繁重的操作很容易对应用程序的性能产生负面影响。最好不要在性能关键代码中使用代理。


参考资料