Published on

设计模式 —— 代理模式

Authors
  • avatar
    Name
    Deng Hua
    Twitter

文章翻译至: Proxy Pattern

目录

代理模式

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

一般来说,代理人是指代替他人的人。您无需直接与该人交谈,而是与代表您试图联系的人的代理人交谈。 JavaScript 中也会发生同样的情况:我们将与 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 的第二个参数是代表处理程序的对象。在处理程序对象中,我们可以根据交互类型定义特定的行为。尽管可以添加到代理处理程序的方法有很多,但最常见的两个是 getset

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

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

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

让我们向 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;
  },
});

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

当访问 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;
    }
  },
});

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

Reflect

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

以前,我们尝试通过直接使用obj[prop]获取或设置值来修改并访问代理内的目标对象上的属性。相反,我们可以使用 Reflect 对象。Reflect 对象上的方法与handler对象上的方法同名。

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

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

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}`);
    return Reflect.set(obj, prop, value);
  }
});

personProxy.name;
personProxy.age = 43;
personProxy.name = "Jane Doe";

权衡

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

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