JavaScript装饰者模式详解:动态扩展对象功能的三种实现方式
本文深入解析JavaScript中的装饰者模式,阐述其在不改变原类结构下动态扩展对象功能的原理。通过三个具体代码实例,详细演示了如何包装对象以添加额外功能,并重点分析了模式实现中this关键字的指向逻辑及对象链式调用的执行过程,帮助开发者掌握该设计模式的核心应用。
装饰者模式概述
引言部分
装饰者模式(Decorator Pattern)的核心定义在于:它允许在不修改原有类定义或破坏继承关系的前提下,动态地为对象添加新的行为。其实现机制是通过将一个对象包裹在另一个具有相同接口的包装对象中,从而生成一个具备扩展功能的新对象。
该模式具备以下关键特征:
1. 功能扩展不改变原对象的结构。
2. 装饰后的对象与原对象共享相同的接口,确保客户端可以以一致的方式使用原对象或装饰后的对象。
3. 装饰对象内部持有对被装饰原对象的引用,即装饰对象本质上是原对象经过包装后的形态。
JavaScript中的装饰者模式实现
二、JavaScript中装饰者模式的详细解析:
场景描述:
在装饰者模式中,核心优势在于能够在运行时动态地为对象附加功能。对于静态类型的语言而言,实现这一机制可能较为复杂;但在JavaScript中,由于对象本身具有可变性,动态添加功能相对直接。
装饰者模式的一个显著优势是其行为的可定制性与可配置性。开发者可以从具备基础功能的普通对象起步,随后从可用的装饰资源池中挑选所需的功能模块,并按特定顺序(尤其是当执行顺序对结果有影响时)对基础对象进行层层装饰。
实现装饰者模式的一种常见策略是将每个装饰器定义为独立的对象,该对象包含需要重载或扩展的方法。每个装饰器实际上继承并增强了前一个装饰器处理后的对象状态。每个装饰方法在执行时,会先调用被包装对象(即“继承的对象”)的同名方法以获取基础值,随后再执行额外的装饰逻辑。
实例1:对象包装与费用计算
首先展示实例1的代码实现:
//需要装饰的类(函数)
function Macbook() {
this.cost = function () {
return 1000;
};
}
//计算商品的包装费
function PackagingFee(macbook) {
this.cost = function () {
return macbook.cost() + 75;
};
}
//计算商品的运费
function Freight(macbook) {
this.cost = function () {
return macbook.cost() + 300;
};
}
//计算商品的保险费用
function Insurance(macbook) {
this.cost = function () {
return macbook.cost() + 250;
};
}
// 用法
var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
console.log(myMacbook.cost());//1625
对上述代码进行简要分析:代码中总共定义了四个函数,其中一个是需要被装饰的基础函数,另外三个则是用于执行装饰操作的函数。
接着,声明一个变量myMacbook指向new出来的Insurance对象,Insurance对象的形参指向new出来的Freight对象,Freight对象的形参指向new出来的PackagingFee对象,PackagingFee对象的形参指向new出来的Macbook对象。
接下来,调用myMacbook的cost方法。从上面的分析,我们可以得出 myMacbook.cost()的值等于(Freight对象的cost方法+250),Freight对象的cost方法等于(PackagingFee对象的cost方法+300),PackagingFee对象的cost方法等于(Macbook对象的cost方法+75)。
所以最终的结果是:myMacbook.cost()的值 = 250 + (300 + (75 + 1000)) = 1625。
// 用法
var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
console.log(myMacbook.cost());//1625
//上面的代码等价于下面拆分后的代码,或许拆分后代码你更能看出前后的逻辑性
var macbook = new Macbook();
var package = new PackagingFee(macbook);
var freight = new Freight(package);
var myMacbook = new Insurance(freight);
//当然,如果你不想声明这么多变量(macbook、package、freight),只用一个变量也是可以的
var macbook = new Macbook();
macbook = new PackagingFee(macbook);
macbook = new Freight(macbook);
var myMacbook = new Insurance(macbook);
实例2:装饰器类与this指向分析
接着观察实例2:
function ConcreteClass() {
this.performTask = function () {
this.preTask();
console.log('doing something');
this.postTask();
};
}
function AbstractDecorator(decorated) {
this.performTask = function () {
decorated.performTask();
};
}
function ConcreteDecoratorClass(decorated) {
this.base = AbstractDecorator;
this.base(decorated);// add performTask method
decorated.preTask = function () {
console.log('pre-calling..');
};
decorated.postTask = function () {
console.log('post-calling..');
};
}
var concrete = new ConcreteClass();
var decorator1 = new ConcreteDecoratorClass(concrete);
decorator1.performTask();
//pre-calling..
//doing something
//post-calling..
实例2在逻辑上与实例1高度相似,此处进行简要剖析。首先,实例2中定义了三个函数,随后声明了两个变量concrete和decorator1,最终调用了decorator1的performTask方法。
初看之下,ConcreteDecoratorClass内部似乎并未直接定义performTask方法。我们首先解析以下两行关键代码:
var concrete = new ConcreteClass(); //声明一个变量concrete指向new出来的ConcreteClass对象
var decorator1 = new ConcreteDecoratorClass(concrete); //声明一个变量decorator1指向new出来的ConcreteDecoratorClass对象,并传入变量concrete作为形参
随后,我们逐行深入分析ConcreteDecoratorClass函数内部的代码逻辑:
this.base = AbstractDecorator; //定义一个当前对象(decorator1)的base属性,并指向函数AbstractDecorator
this.base(decorated); //调用base属性指向的函数,也就是调用AbstractDecorator函数,同时传入形参decorated,形参decorated指向new出来的ConcreteClass对象
至此,似乎仍未在ConcreteDecoratorClass函数中直接找到performTask方法的定义,关键在于理解“this”关键字的作用域。
ConcreteDecoratorClass函数中的this指向new出来的ConcreteDecoratorClass对象(也就是和decorator1指向同一个对象);
AbstractDecorator函数里面的this关键是看哪个对象来调用这个函数,this就指向哪个对象(从代码 “this.base = AbstractDecorator; this.base(decorated);” 中我们可以看出是new出来的ConcreteDecoratorClass对象在调用AbstractDecorator函数),所以AbstractDecorator函数里面的this指向new出来的ConcreteDecoratorClass对象(也和decorator1指向同一个对象)。
总结下来,我们会发现,在上面的代码中,不管是ConcreteDecoratorClass函数里面的this,还是AbstractDecorator函数里面的this,都指向new出来的ConcreteDecoratorClass对象。
因此,当我们执行decorator1.performTask()时,它会继续执行匿名函数中的代码(decorated.performTask();),匿名函数中的decorated形参指向new出来的ConcreteClass对象,并执行该对象的performTask方法。
实例3:复杂装饰逻辑解析
最后查看实例3:
var tree = {};
tree.decorate = function () {
console.log('Make sure the tree won\'t fall');
};
tree.getDecorator = function (deco) {
tree[deco].prototype = this;
return new tree[deco];
};
tree.RedApples = function () {
this.decorate = function () {
this.RedApples.prototype.decorate(); // 第7步:先执行原型(这时候是Angel了)的decorate方法
console.log('Add some red apples'); // 第8步 再输出 red
// 将这2步作为RedApples的decorate方法
}
};
tree.BlueApples = function () {
this.decorate = function () {
this.BlueApples.prototype.decorate(); // 第1步:先执行原型的decorate方法,也就是tree.decorate()
console.log('Put on some blue apples'); // 第2步 再输出blue
// 将这2步作为BlueApples的decorate方法
}
};
tree.Angel = function () {
this.decorate = function () {
this.Angel.prototype.decorate(); // 第4步:先执行原型(这时候是BlueApples了)的decorate方法
console.log('An angel on the top'); // 第5步 再输出angel
// 将这2步作为Angel的decorate方法
}
};
tree = tree.getDecorator('BlueApples'); // 第3步:将BlueApples对象赋给tree,这时候父原型里的getDecorator依然可用
tree = tree.getDecorator('Angel'); // 第6步:将Angel对象赋给tree,这时候父原型的父原型里的getDecorator依然可用
tree = tree.getDecorator('RedApples'); // 第9步:将RedApples对象赋给tree
tree.decorate(); // 第10步:执行RedApples对象的decorate方法
//Make sure the tree won't fall
//Add blue apples
//An angel on the top
//Put on some red apples
实例3表面看似复杂,但其分析逻辑与前两个实例一致。我们可以看出实例3中一共声明了5个函数表达式。我们重点分析下面的代码片段:
//tree.getDecorator('BlueApples')返回new出来的tree.BlueApples的实例对象,并将该对象赋值给空的tree对象
tree = tree.getDecorator('BlueApples'); //new出来的tree.BlueApples的实例对象的原型指向 --> 空对象tree
//tree.getDecorator('Angel')返回new出来的tree.Angel的实例对象(这行代码中的第二个tree已经是上面一行代码运行结果后的tree.BlueApples的实例对象)
tree = tree.getDecorator('Angel'); //new出来的tree.Angel的实例对象的原型指向 --> tree.BlueApples的实例对象
//tree.getDecorator('RedApples')返回new出来的tree.RedApples的实例对象(这行代码中的第二个tree已经是上面一行代码运行结果后的tree.Angel的实例对象)
tree = tree.getDecorator('RedApples'); //new出来的tree.RedApples的实例对象的原型指向 --> tree.Angel的实例对象
//调用tree.decorate(),这里的tree已经是new出来的tree.RedApples的实例对象了。
//tree.RedApples的实例对象的decorate属性方法里面的第一行代码是 “this.RedApples.prototype.decorate()”
//结合上面的分析可以得出以下的原型链结构:
//this.RedApples.prototype --> tree.Angel;
//tree.Angel.prototype --> tree.BlueApples;
//tree.BlueApples.prototype --> 空对象tree
tree.decorate();
经过上述分析,最终的输出结果便清晰可见。
注意事项与建议
三、补充说明:
通过观察本文中的装饰者模式案例,可以发现大量使用了this关键字。对此概念尚不熟悉的读者,建议参考《深入理解javascript中的 “this”》一文以加深理解。
建议读者将本文中的案例代码复制并逐行调试分析,立即动手实践以巩固知识!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持本站!
怎么用 Promise 封装 JavaScript sleep 函数实现异步延时
2026-11-05 17:49:14
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
