JavaScript设计模式是为了解决JavaScript代码中常见问题的可重用解决方案。
以下是一些常见的JavaScript设计模式:
一、单例模式(Singleton Pattern)
确保一个类只有一个实例,并提供一个全局访问点。在 JavaScript 中,可以使用闭包来实现单例模式。
var Singleton = (function() {
var instance;
function createInstance() {
var object = new Object("I am the instance");
return object;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// 使用
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
在这个例子中,Singleton
是一个立即执行函数,它返回一个对象,其中包含一个getInstance
方法。getInstance
方法首先检查是否已经存在实例,如果没有,则创建一个新的实例并返回它。如果已经存在实例,则直接返回它。
由于 JavaScript 中的函数都是闭包,所以instance
变量可以被createInstance
和getInstance
方法共享,这样就可以保证只有一个实例存在。
注意:在使用单例模式时,需要注意不要滥用它,否则会导致代码变得复杂和难以维护。
二、工厂模式(Factory Pattern)
JavaScript 工厂模式是一种创建对象的设计模式,它通过工厂方法来创建和返回对象,而无需直接在代码中使用 new 关键字。工厂模式可以将对象的创建与使用分离,使得代码更加灵活和可维护。
function createPerson(name, age) {
const person = {};
person.name = name;
person.age = age;
person.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
};
return person;
}
const john = createPerson('John', 30);
john.sayHello(); // 输出 "Hello, my name is John and I'm 30 years old."
在这个示例中,我们定义了一个名为 createPerson 的工厂函数,用于创建 Person 对象。该函数接受两个参数:name 和 age,用于设置 Person 对象的属性。在函数内部,我们使用一个空对象 person 来创建 Person 对象,并将其属性设置为传递的参数。最后,我们将一个新的方法 sayHello 添加到 person 上,并将其返回。
通过使用工厂模式,我们可以通过调用 createPerson 函数来创建多个 Person 对象,而无需重复编写相同的代码。
三、观察者模式(Observer Pattern)
又叫做发布-订阅模式(Publish-Subscribe Pattern);定义了对象之间的一对多依赖关系,使得当一个对象改变状态时,所有依赖它的对象都会被通知并自动更新。
// 主题对象
class Subject {
constructor() {
this.observers = [];
}
// 添加观察者
addObserver(observer) {
this.observers.push(observer);
}
// 移除观察者
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
// 通知观察者
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
// 观察者对象
class Observer {
update(data) {
console.log(`Received data: ${data}`);
}
}
// 创建主题对象
const subject = new Subject();
// 创建观察者对象
const observer1 = new Observer();
const observer2 = new Observer();
// 添加观察者
subject.addObserver(observer1);
subject.addObserver(observer2);
// 通知观察者
subject.notify('Hello world!');
在这个示例中,我们创建了一个主题对象Subject
和两个观察者对象Observer
,然后将观察者对象添加到主题对象的观察者列表中。当主题对象的状态发生变化时,它会通知观察者对象,并执行它们的更新方法。在这个示例中,我们通过调用subject.notify('Hello world!')
来通知观察者对象,并将字符串'Hello world!'
作为参数传递给观察者对象的更新方法。
四、装饰者模式(Decorator Pattern)
用于动态地给一个对象添加额外的职责,同时又不影响该对象的其他功能。
// 定义一个普通的对象
const obj = {
name: 'John',
sayHello: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
// 定义一个装饰器函数
function addAge(obj, age) {
obj.age = age;
obj.sayAge = function() {
console.log(`I am ${this.age} years old`);
};
return obj;
}
// 使用装饰器函数为对象添加新的属性和方法
const decoratedObj = addAge(obj, 30);
// 调用对象的方法和新添加的方法
decoratedObj.sayHello(); // 输出:Hello, my name is John
decoratedObj.sayAge(); // 输出:I am 30 years old
在上面的例子中,我们定义了一个普通的对象 obj
,然后使用装饰器函数 addAge
为它添加了新的属性 age
和方法 sayAge
。通过这种方式,我们成功地扩展了对象 obj
的功能,而不需要修改原始对象的代码。这个例子只是装饰器模式的一个简单示例,实际应用中可能会更加复杂。
五、适配器模式(Adapter Pattern)
将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能在一起工作的类可以协同工作。
参考:https://aiguangyuan.blog.csdn.net/article/details/103212133
var chinaPlug = {
type: '中国插头',
chinaInPlug() {
console.log('开始供电');
}
};
var japanPlug = {
type: '日本插头',
japanInPlug() {
console.log('开始供电');
}
};
// 日本插头电源适配器
function japanPlugAdapter(plug) {
return {
chinaInPlug() {
return plug.japanInPlug();
}
}
};
japanPlugAdapter(japanPlug).chinaInPlug();
// 开始供电
六、策略模式(Strategy Pattern)
定义了一系列算法,并将每个算法封装起来,使它们可以互相替换,使得算法的变化不会影响到使用算法的客户。
const StrategyMap = {};
StrategyMap.minus100_30 = function(price) {
return price - Math.floor(price / 100) * 30
};
function context(type, ...rest) {
return StrategyMap[type] && StrategyMap[type](...rest)
};
context('minus100_30', 270);
// 210
根据上面的例子提炼一下策略模式,折扣计算方式可以被认为是策略(Strategy),这些策略之间可以相互替代,而具体折扣的计算过程可以被认为是封装上下文(Context),封装上下文可以根据需要选择不同的策略。
主要有下面几个概念:
1. Context :封装上下文,根据需要调用需要的策略,屏蔽外界对策略的直接调用,只对外提供一个接口,根据需要调用对应的策略;
2. Strategy :策略,含有具体的算法,其方法的外观相同,因此可以互相代替;
3. StrategyMap :所有策略的合集,供封装上下文调用;
七、命令模式(Command Pattern)
将一个请求封装为一个对象,使得可以用不同的请求对客户进行参数化,同时将请求排队或记录请求日志,以及支持可撤销的操作。
八、职责链模式(Chain of Responsibility Pattern)
将请求的发送者和接收者解耦,使得多个对象都有机会处理这个请求,从而避免了请求发送者需要知道哪一个对象会处理请求的问题。
九、模板方法模式(Template Method Pattern)
定义了一个操作中的算法骨架,将一些步骤延迟到子类中实现,使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
十、迭代器模式(Iterator Pattern)
提供一种访问一个容器对象中各个元素的方法,而又不暴露该对象的内部细节。
十一、代理模式(Proxy Pattern)
又称委托模式;为另一个对象提供一个替身或占位符,以便控制对原对象的访问。可以通过代理对象来控制对原对象的访问权限,或者在访问前后添加一些额外的逻辑。
// 明星
const SuperStar = {
name: '小鲜肉',
// 档期标识位,false-没空,true-有空
scheduleFlag: false,
playAdvertisement(ad) {
console.log(ad)
}
}
// 经纪人
const ProxyAssistant = {
name: '经纪人',
scheduleTime(ad) {
// 在这里监听 scheduleFlag 值的变化
const schedule = new Proxy(SuperStar, {
set(obj, prop, val) {
if (prop !== 'scheduleFlag') {return};
// 小鲜肉现在有空了
if (obj.scheduleFlag === false && val === true) {
obj.scheduleFlag = true;
// 安排上了
obj.playAdvertisement(ad);
}
}
});
setTimeout(() => {
console.log('小鲜鲜有空了');
// 明星有空了
schedule.scheduleFlag = true
}, 2000)
},
playAdvertisement(reward, ad) {
// 如果报酬超过100W
if (reward > 1000000) {
console.log('没问题,我们小鲜肉最喜欢拍广告了')
ProxyAssistant.scheduleTime(ad);
} else{
console.log('没空,滚!');
}
}
}
ProxyAssistant.playAdvertisement(10000, '纯蒸酸牛奶,味道纯纯,尽享纯蒸');
// 没空,滚
ProxyAssistant.playAdvertisement(1000001, '纯蒸酸牛奶,味道纯纯,尽享纯蒸');
// 没问题,我们小鲜肉最喜欢拍广告了
// 小鲜肉有空了
// 纯蒸酸牛奶,味道纯纯,尽享纯蒸
以上是一些常见的JavaScript设计模式,它们可以帮助我们提高代码的可重用性、可维护性和可扩展性。