在我的之前的文章里,我探讨了各种创建模式,这些模式关注的是如何创建对象。现在,让我们深入探讨结构型模式,这些模式关注的是对象和类如何组合成更大的结构,同时保持灵活性和高效。我们先来看看代理模式
JavaScript中的代理设计模式——一种设计模式,用于间接地访问对象代理设计模式是一种结构型设计模式,它提供一个对象来代表另一个对象。它充当一个中介,控制对实际对象的访问,并添加诸如延迟加载、日志记录、访问控制或缓存之类的功能,而不修改原始对象的代码。
在 JavaScript 中,代理 是通过 Proxy 对象提供的内置功能,允许您为诸如属性访问、赋值和函数调用等基本操作定义自己想要的行为。
什么时候我们需要用到代理模式?代理模式尤其有用,尤其是在以下几个情况:
- 懒惰初始化:您希望延迟创建资源密集型对象,直到需要它时。
- 访问控制:您需要控制对对象的访问,例如,限制未经授权的访问或根据条件限制操作的执行。
- 日志记录:对对象的操作进行记录(例如,访问属性或调用方法)。
- 缓存:您希望缓存高成本操作的结果以避免冗余计算。
- 主题: 定义了真实对象和代理共有的操作的接口。
- 真实对象: 执行实际工作的实际对象。
- 代理: 控制对真实对象访问的中介。
想象你有一幅大型画作想展示给客人,但它很重,每次从储藏室搬出来都要花很长时间。为了避免每次都要等这么长时间,你决定用这幅画的小明信片快速展示给客人,让他们在等待真正画作被拿出来的时候有东西看。
在这个类比中:
- 大画是真正的对象(就像一个加载慢的图像)。
- 明信片是替代品(一个轻便的替代品,直到真正的画准备好了)。
- 一旦真正的画准备好,你就把那幅真的画展示给客人看。
现实世界的比喻:
可以把房地产经纪人想象成一个代理。当你想要购买房子时,你并不会立即去参观每一栋房子。相反,房地产经纪人会先向你展示房子的照片和描述。只有当你决定真的要看房时,经纪人会安排你去看房子。
实际案例:图片加载(虚拟代理模式)让我们以 web 应用中图片加载为例,希望在用户请求时再加载图片(即延迟加载),这时代理服务器可以先作为占位符,直到真正图片加载完成。
这里是如何用 JavaScript 实现代理设计模式的方法。
示例:图像加载代理
// 步骤 1:真实对象
class RealImage {
constructor(filename) {
this.filename = filename;
this.loadImageFromDisk();
}
loadImageFromDisk() {
console.log(`从磁盘加载 ${this.filename}...`);
}
display() {
console.log(`显示 ${this.filename}`);
}
}
// 步骤 2:代理图像
class ProxyImage {
constructor(filename) {
this.realImage = null; // 尚未加载实际图像
this.filename = filename;
}
display() {
if (this.realImage === null) {
// 只在需要时加载实际图像
this.realImage = new RealImage(this.filename);
}
this.realImage.display(); // 显示实际图像
}
}
// 步骤 3:使用代理显示图像
const image = new ProxyImage("photo.jpg");
image.display(); // 首次加载并显示图像
image.display(); // 再次显示图像(已加载)
全屏 全屏退出
解释:
1). 真正的图:
RealImage
类表示实际的图像。- 它接收一个文件名作为输入参数,并通过
loadImageFromDisk
方法来模拟从磁盘加载图像的过程。 - 加载完成后,调用
display
方法来显示图像。
2). 代理图片:
ProxyImage
类作为RealImage
的替代物。它不会立即加载真实的图像。- 它保留对真实图像的引用(但最初是
null
,因为真实图像还未加载)。 - 当你调用代理的
display
方法时,它会检查是否已加载真实图像。如果没有加载,它会先加载真实图像,再显示。
3). 用法:
- 当我们创建一个
ProxyImage
的实例时,实际图像尚未加载(因为加载它会消耗大量资源)。 - 第一次调用
display
方法时,代理会使用RealImage
类加载图像并显示它。 - 第二次调用
display
方法时,由于图像已经加载,它只会显示而不会重新加载图像。
ES6 代理使用一个构造器,这个构造器接受一个目标对象和一个处理函数作为参数。
const 代理 = new Proxy(目标, 处理器)
全屏模式 退出全屏
在这里,target
表示代理被应用的对象,而 handler 是一个定义了代理对象行为的对象。
处理器对象包含一系列可选的陷阱方法,这些方法具有预定义名称(例如 apply、get、set 和 has 等),当在代理实例上执行相应操作时,这些方法将被自动调用。
让我们通过使用内置的代理功能来实现一个计算器功能,从而更好地理解这一点。
// 步骤1:定义具有方法的Calculator类
class Calculator {
constructor() {
this.result = 0;
}
// 方法:加法
add(a, b) {
this.result = a + b;
return this.result;
}
// 方法:减法
subtract(a, b) {
this.result = a - b;
return this.result;
}
// 方法:乘法
multiply(a, b) {
this.result = a * b;
return this.result;
}
// 方法:除法
divide(a, b) {
if (b === 0) throw new Error("除数不能为零。");
this.result = a / b;
return this.result;
}
}
// 步骤2:创建一个代理处理器以拦截操作
const handler = {
// 拦截'get'操作以确保访问方法
get(target, prop, receiver) {
if (prop in target) {
console.log(`访问属性:${prop}`);
return Reflect.get(target, prop, receiver); // 安全地访问属性
} else {
throw new Error(`属性"${prop}"不存在。`);
}
},
// 拦截'set'操作以防止修改
set(target, prop, value) {
throw new Error(`无法修改属性 "${prop}"。这是因为计算器是不可变的。`);
}
};
// 步骤3:创建一个继承Calculator原型的代理实例
const calculator = new Calculator(); // 原始计算器对象
const proxiedCalculator = new Proxy(calculator, handler); // 包裹计算器的代理
// 步骤4:使用代理实例
try {
console.log(proxiedCalculator.add(5, 3)); // 输出:8
console.log(proxiedCalculator.multiply(4, 2)); // 输出:8
console.log(proxiedCalculator.divide(10, 2)); // 输出:5
// 尝试直接通过代理访问原型(应该输出 true)
console.log(proxiedCalculator.__proto__ === Calculator.prototype); // 输出:true
// 尝试修改属性(应该会抛出异常)
proxiedCalculator.result = 100; // 异常:无法修改属性 "result"。这是因为计算器是不可变的。
} catch (error) {
console.error(error.message); // 输出:无法修改属性 "result"。这是因为计算器是不可变的。
}
进入全屏 退出全屏
这种方式使用代理的最佳部分如下:
- 代理对象继承了原始 Calculator 类的原型,从而。
- 利用 Proxy 的 set 陷阱来避免修改原始对象。
代码解释:对代码的解释
1). 原型继承机制:
- 代理不会影响原始的 Calculator 类的原型,
- 这可以通过检查 proxiedCalculator.prototype 是否等于 Calculator.prototype 并返回 true(为真)来确认。
2). 获取get
操作(获取操作):
- 获取拦截器拦截了代理对象上的属性访问。
- 我们利用
Reflect.get
安全地访问原始对象的属性及方法。
3). 防止变异:
set
陷阱机制每当尝试修改目标对象的任何属性时抛出错误,这可以确保对象的不可变。
4). 通过代理来使用原型模式:
该代理对象允许访问如add
、subtract
、multiply
和divide
这样的方法,这些方法都是在原始对象的原型上定义的。
这里的关键是:
- 保留原型继承特性: 代理保留了对所有原型方法的访问,使其表现得和原来的计算器一样。
- 防止篡改内部状态: 设置陷阱确保计算器对象的内部状态不会意外改变。
- 安全访问属性和方法: 获取陷阱确保只访问有效的属性,提高了健壮性,使系统更可靠。
如果你看到这里,不要忘了点个赞 ❤️,也可以在下面留言区写留言,有任何问题或想法都可以说说。你的反馈对我来说非常重要,我很希望听听你的看法!
共同學習,寫下你的評論
評論加載中...
作者其他優質文章