关于 proxy-pattern
一项讲解 JavaScript Proxy 模式的学习技能,用于拦截对象属性的读取与修改。内容涵盖验证、日志、Reflect 用法及性能权衡。
主要功能
- 01拦截属性读取与写入
- 02验证更新后的值
- 03记录属性访问与变更
- 04使用 Reflect 操作目标对象
- 05了解性能权衡
适用场景
- 01更新时验证对象字段
- 02记录读写以辅助调试
- 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 方法时执行大量操作,都很容易对应用的性能造成负面影响。最好不要在性能关键型代码中使用代理。
来源
参考资料
- Proxy - MDN
- JavaScript Proxy - David Walsh
- Awesome ES2015 Proxy - GitHub @mikaelbr
- Thoughts on ES6 Proxies Performance - Valeri Karpov