在 JavaScript 世界里,设计模式常常被包装得高深莫测,但其实它就藏在我们日常的代码场景中。今天我们从一个生活化的例子 ——"送花" 说起,聊聊代理模式(Proxy)到底是什么,以及它在 JS 中如何用简单的对象字面量轻松实现。
一、一个扎心的送花场景
先看一段代码,这是一个典型的面向对象实现:
// 郑志鹏对象
let zzp = {
name: '郑志鹏',
sendFlower: function(target) {
target.receiveFlower(zzp); // 给目标送花
}
};
// 小美对象
let xm = {
xq: 30, // 心情值,满分为100
name: '小美',
receiveFlower: function(sender) {
console.log('小美收到了' + sender.name + '的花');
if (this.xq < 80) {
console.log('不约,我们不约'); // 心情差就拒绝
} else {
console.log('硕果走一波!!!'); // 心情好就接受
}
}
};
如果郑志鹏直接给小美送花:
zzp.sendFlower(xm);
// 输出:小美收到了郑志鹏的花 → 不约,我们不约
结果很扎心,因为小美心情值太低。这时候问题来了:如何在不改变小美和郑志鹏原有代码的情况下,提高送花成功率?
二、代理模式登场:小红的中间人作用
现实中遇到这种情况,我们可能会找个中间人帮忙打探情况。在代码世界里,这个中间人就是 "代理":
// 小红作为代理
let xh = {
name: '小红',
receiveFlower: function(sender) {
// 3秒后再转发(模拟等待时机)
setTimeout(() => {
xm.xq = 90; // 小红帮忙调节小美心情
xm.receiveFlower(sender); // 转发花
}, 3000);
}
};
现在郑志鹏通过小红送花:
zzp.sendFlower(xh);
// 3秒后输出:小美收到了郑志鹏的花 → 硕果走一波!!!
奇迹发生了!这里的小红就是一个代理对象,她实现了和小美相同的 receiveFlower 方法(这就是接口一致性),却在中间做了额外操作(调节心情)。
三、代理模式的核心:接口一致性 + 增强行为
从这个例子里,我们能提炼出代理模式的核心要素:
-
接口一致性:代理对象(小红)和目标对象(小美)必须拥有相同的方法(
receiveFlower),这样调用者(郑志鹏)才不需要区分到底在调用谁。这就是面向接口编程的精髓 —— 调用者只关心 "能做什么",不关心 "具体是谁在做"。 -
增强行为:代理可以在不修改目标对象的前提下,添加额外操作。比如小红做的:
- 延迟执行(等待合适时机)
- 修改目标状态(提高心情值)
- 甚至可以拒绝转发(如果判断送花肯定失败)
-
职责分离:目标对象(小美)只负责核心逻辑(收花后的反应),代理(小红)负责辅助逻辑(时机判断、状态调节),符合单一职责原则。
四、JavaScript 中的代理模式优势
为什么 JS 特别适合实现代理模式?这和它的语言特性密不可分:
- 对象字面量的灵活性:不需要像 Java 那样先定义接口类,直接用
{}就能创建拥有相同方法的对象,轻松实现接口一致性。 - 函数是一等公民:方法可以像变量一样传递,让代理的方法转发变得极其简单。
- 弱类型特性:不需要严格的类型检查,只要两个对象有相同方法就能互相替代,降低了代理模式的实现成本。
五、最后一些自己的思考
初学设计模式时,很容易陷入 "为了用模式而用模式" 的误区。但从送花的例子能看出:
代理模式的本质是 "控制访问" —— 当直接访问目标对象不方便或有问题时,就引入代理。它不是要增加复杂度,而是通过合理的职责拆分让代码更清晰、更灵活。
在 JavaScript 中,我们不必纠结于严格的设计模式规范,用对象字面量 + 函数就能轻松实现代理模式的核心思想。这种灵活性正是 JS 作为脚本语言的魅力所在 —— 不需要繁琐的类定义,就能写出优雅的面向对象代码。
最后用一句话总结:代理模式就像生活中的中间人,帮我们解决直接沟通的痛点,同时还不改变原有的沟通方式。下次遇到需要增强对象行为又不想修改原代码的场景,不妨想想这个送花的故事!