mcpField
P
proxy-pattern

proxy-pattern

github.com/patternsdev开发257613 次安装28 周前更新
代码仓库

关于 proxy-pattern

一项讲解 JavaScript Proxy 模式的学习技能,用于拦截对象属性的读取与修改。内容涵盖验证、日志、Reflect 用法及性能权衡。

主要功能

  1. 01拦截属性读取与写入
  2. 02验证更新后的值
  3. 03记录属性访问与变更
  4. 04使用 Reflect 操作目标对象
  5. 05了解性能权衡

适用场景

  1. 01更新时验证对象字段
  2. 02记录读写以辅助调试
  3. 03为对象访问添加格式化或通知

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/patternsdev/skills --skill proxy-pattern

安装后在 Claude Code 中输入/proxy-pattern确认已加载。

详细介绍

代理模式

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

适用场景

  • 当你需要在访问对象时添加验证、格式化、通知或调试功能
  • 当你需要控制和拦截对象属性的读取和设置时

不适用场景

  • 在对性能要求极高的热点路径中,Proxy 在每次属性访问时产生的开销会造成影响
  • 当简单的 getter/setter 方法或 Object.defineProperty 可以用更少的间接层实现相同的验证时
  • 当目标对象很少被访问,且不需要拦截逻辑时

说明

  • 创建一个带有处理器对象的 Proxy,并在其中定义 get 和 set 陷阱
  • 在处理器中使用 Reflect 对象,以更简洁地访问和修改属性
  • 在 set 陷阱中添加验证逻辑,以确保数据完整性
  • 避免在对性能要求极高的代码路径中使用代理,因为它会增加开销

详情

一般来说,代理是某人的替身。你不会直接和那个人交谈,而是与代表你想联系的那个人的代理交谈。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 的第二个参数是一个表示 处理器 的对象。在处理器对象中,我们可以根据交互类型定义特定行为。虽然你可以向 Proxy 处理器添加许多方法,但最常见的是 get 和 set:

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

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

我们来为 personProxy Proxy 添加处理器。尝试修改属性时,会调用 Proxy 上的 set 方法,我们希望代理记录该属性的旧值和新值。尝试访问属性时,会调用 Proxy 上的 get 方法,我们希望代理记录一条更易读的语句,其中包含属性的键和值。

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

访问 name 属性时,Proxy 返回了一句更自然的话:The value of name is John Doe。

修改 age 属性时,Proxy 返回了该属性的旧值和新值: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]}`);
    }
    return 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;
    }
    return true;
  },
});

代理确保我们不会用无效值修改 person 对象,从而帮助我们保持数据的纯净!

Reflect

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

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

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

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

借助 Reflect 对象,我们可以轻松访问和修改目标对象上的属性。

权衡

代理是一种强大的方式,可用于控制对象的行为。代理有多种用途:可以用于验证、格式化、通知或调试。

过度使用 Proxy 对象,或在每次调用 handler 方法时执行大量操作,都很容易对应用的性能造成负面影响。最好不要在性能关键型代码中使用代理。

来源

参考资料

评论

支持 Markdown