前置知识
构造函数
普通函数
在 JavaScript 中,很多时候,你需要避免使用 new 关键字。
类
单例模式
顾名思义,一个类确保只有一个实例,供全局访问
实现单例模式通常包括以下关键步骤:
- 私有构造函数:确保单例类的构造函数不能从外部直接调用,以防止创建多个实例。
- 静态实例属性:在类定义中创建一个静态属性来存储单例实例。
- 全局访问点:提供一个全局访问点,通常是通过一个静态方法获取单例实例。
- 懒加载或饿汉式加载:根据需要确定是立即创建实例(饿汉式)还是首次请求时创建(懒加载)。
实际应用
vuex仓库,闭包
工厂模式
通俗点讲
工厂模式,就像是一个提供各种产品的商店。你告诉店员你想要什么产品,店员就会给你制作或者拿给你,而你并不需要知道这个产品是怎么制作出来的。
举例
假设我们正在开发一个电商网站,网站中有多种类型的商品,比如衣服、鞋子和电子产品。每个商品都有不同的属性和价格。我们可以使用工厂模式来创建这些商品对象。
首先,我们可以定义一个抽象的商品类(Product),它包含一些通用的属性和方法:
然后,我们可以定义几个具体的商品类,比如衣服(Clothing)、鞋子(Shoes)和电子产品(Electronics),它们都继承自商品类(Product):
接下来,我们可以定义一个工厂类(ProductFactory),它负责根据传入的参数创建不同类型的商品对象:
最后,我们可以使用工厂类来创建不同类型的商品对象:
使用工厂模式完整代码
// 定义抽象类
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
getDetails() {
return `${this.name}的价格是${this.price}`;
}
}
// 定义具体商品类
class Clothing extends Product {
constructor(name, price, size) {
super(name, price);
this.size = size;
}
}
class Shoes extends Product {
constructor(name, price, brand) {
super(name, price);
this.brand = brand;
}
}
class Electronics extends Product {
constructor(name, price, warranty) {
super(name, price);
this.warranty = warranty;
}
}
// 工厂类
class ProductFactory {
static createProduct(type, name, price, extraParam) {
switch (type) {
case 'clothing':
return new Clothing(name, price, extraParam);
case 'shoes':
return new Shoes(name, price, extraParam);
case 'electronics':
return new Electronics(name, price, extraParam);
default:
throw new Error('Invalid product type');
}
}
}
const clothing = ProductFactory.createProduct('clothing', 'T-shirt', 50, 'M');
console.log(clothing.getDetails()); // 输出:T-shirt的价格是50
const shoes = ProductFactory.createProduct('shoes', 'Running Shoes', 200, 'Nike');
console.log(shoes.getDetails()); // 输出:Running Shoes的价格是200
const electronics = ProductFactory.createProduct('electronics', 'Laptop', 1500, '1 Year Warranty');
console.log(electronics.getDetails()); // 输出:Laptop的价格是1500
不使用工厂模式完整代码
// 定义抽象类
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
getDetails() {
return `${this.name}的价格是${this.price}`;
}
}
// 定义具体商品类
class Clothing extends Product {
constructor(name, price, size) {
super(name, price);
this.size = size;
}
}
class Shoes extends Product {
constructor(name, price, brand) {
super(name, price);
this.brand = brand;
}
}
class Electronics extends Product {
constructor(name, price, warranty) {
super(name, price);
this.warranty = warranty;
}
}
// 直接实例化各个具体商品类
const clothing = new Clothing('T-shirt', 50, 'M');
console.log(clothing.getDetails()); // 输出:T-shirt的价格是50
const shoes = new Shoes('Running Shoes', 200, 'Nike');
console.log(shoes.getDetails()); // 输出:Running Shoes的价格是200
const electronics = new Electronics('Laptop', 1500, '1 Year Warranty');
console.log(electronics.getDetails()); // 输出:Laptop的价格是1500
两种方式对比,虽然在代码上看,不使用工厂模式更简单,但是在使用工厂模式时,将对象的创建过程封装在工厂类中,使代码更加模块化和可维护,不使用工厂模式会导致创建对象逻辑分散在各个地方。
建造者模式
它可以让你构建复杂的对象,同时保持代码的可读性和易于维护。这种模式将对象的构造过程与其表示分离,使得同样的构建过程可以创建不同的表示。
使用建造者模式时
class CarBuilder {
constructor() {
this.car = {};
}
setBrand(brand) {
this.car.brand = brand;
return this;
}
setModel(model) {
this.car.model = model;
return this;
}
setColor(color) {
this.car.color = color;
return this;
}
build() {
return new Car(this.car);
}
}
class Car {
constructor(car) {
this.brand = car.brand;
this.model = car.model;
this.color = car.color;
}
}
// 使用建造者模式创建车辆对象
const builder = new CarBuilder();
const myCar = builder.setBrand("Toyota").setModel("Camry").setColor("Red").build();
console.log(myCar); // 输出:{ brand: 'Toyota', model: 'Camry', color: 'Red' }
不使用建造者模式时
class Car {
constructor(brand, model, color) {
this.brand = brand;
this.model = model;
this.color = color;
}
}
// 创建车辆对象
const myCar = new Car("Toyota", "Camry", "Red");
对比两者,建造者模式适合复杂的构建对象处理,大概就是 创建过程封装在一个单独的类中 构建时相互不影响,而简单的构建对象,不建议使用建造者模式,多余
原型模式
它通过复制现有对象来创建新对象,而不是通过实例化类来创建。在JavaScript中,原型模式可以通过原型链实现。
使用原型模式时
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 为构造函数的原型添加方法
Person.prototype.sayHello = function () {
console.log('Hello, my name is ' + this.name + ' and I am ' + this.age + ' years old.');
};
// 创建一个Person实例
var person1 = new Person('Alice', 30);
// 调用实例的方法
person1.sayHello(); // 输出: Hello, my name is Alice and I am 30 years old.
// 使用原型模式创建另一个Person实例
var person2 = Object.create(Person.prototype);
person2.name = 'Bob';
person2.age = 25;
// 调用实例的方法
person2.sayHello(); // 输出: Hello, my name is Bob and I am 25 years old.
不使用原型模式时
// 定义一个构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 为构造函数添加方法
Person.prototype.sayHello = function() {
console.log('Hello, my name is ' + this.name + ' and I am ' + this.age + ' years old.');
};
// 创建一个Person实例
var person1 = new Person('Alice', 30);
// 调用实例的方法
person1.sayHello(); // 输出: Hello, my name is Alice and I am 30 years old.
// 创建另一个Person实例
var person2 = new Person('Bob', 25);
// 调用实例的方法
person2.sayHello(); // 输出: Hello, my name is Bob and I am 25 years old.
通俗点讲
原型模式的意义在于允许通过复制现有的对象来创建新的对象,而不是通过调用构造函数来实例化。这种方式有几个显著的优点:
- 减少构造函数的调用:通过复制已有对象来创建新对象,可以减少构造函数的调用次数,这在构造函数执行成本较高时尤其有用。
- 节省资源:如果类的初始化过程消耗资源较多,例如涉及到大量的内存分配或者其他昂贵的操作,使用原型模式可以避免这些开销,因为它是通过复制已有的对象来创建新对象。
- 隐藏创建细节:客户端代码不需要了解对象的创建细节,只需要知道原型对象。这样,即使创建过程复杂,客户端代码也可以简单地通过复制原型来获取新对象。
- 动态添加或删除属性:原型模式允许在运行时动态地添加或删除属性,这提供了更大的灵活性。
- 支持批量操作:有时候需要创建一系列相似或者相关的对象,原型模式可以方便地通过克隆原型来实现这一点。
总的来说,原型模式提供了一种灵活且高效的对象创建方式,特别适用于那些创建成本高或需要频繁创建的场景。
装饰器模式
允许在不修改原始对象的情况下,动态地给对象添加新的功能。
通俗地说,装饰器模式就像给一个已有的物品(对象)添加一些额外的装饰(功能),使得这个物品变得更加丰富和有用
通俗代码,
不使用装饰器模式
使用装饰器模式,将逻辑分开 实现
适配器模式
用于将一个类的接口转换成客户端期望的另一个接口。它通过创建一个适配器类来实现这种转换,使得原本不兼容的接口可以协同工作。
举个例子,假设我们有一个旧的打印机接口,它只能打印黑白文本,而我们现在需要一个能够打印彩色文本的新打印机。我们可以使用适配器模式来解决这个问题。
// 定义一个旧的打印机接口
class OldPrinter {
printText(text) {
console.log("打印黑白文本: " + text);
}
}
// 定义一个新的打印机接口
class NewPrinter {
printColorText(text, color) {
console.log("打印彩色文本: " + text + ",颜色:" + color);
}
}
// 创建一个适配器类,将旧的打印机接口转换为新的打印机接口
class PrinterAdapter extends OldPrinter {
constructor(newPrinter) {
super();
this.newPrinter = newPrinter;
}
printText(text) {
this.newPrinter.printColorText(text, "黑色");
}
}
// 最后,我们可以使用适配器类来适配旧的打印机接口,使其能够打印彩色文本
const oldPrinter = new OldPrinter();
const newPrinter = new NewPrinter();
const printerAdapter = new PrinterAdapter(newPrinter);
oldPrinter.printText("Hello, world!"); // 输出:打印黑白文本: Hello, world!
printerAdapter.printText("Hello, world!"); // 输出:打印彩色文本: Hello, world!,颜色:黑色
不使用适配器模式
class OldPrinter {
printText(text) {
console.log("打印黑白文本: " + text);
}
printColorText(text, color) {
console.log("打印彩色文本: " + text + ",颜色:" + color);
}
}
const PrinterAdapter = new OldPrinter();
PrinterAdapter.printText("Hello, world!"); // 输出:打印黑白文本: Hello, world!
PrinterAdapter.printText("Hello, world!","黑色"); // 输出:打印彩色文本: Hello, world!,颜色:黑色
发布-订阅模式
它允许对象之间进行松耦合的通信。在这种模式中,一个对象(称为发布者)会向多个其他对象(称为订阅者)发送消息,而不需要知道这些订阅者的具体实现细节。
通俗地说,发布-订阅模式就像是报纸和读者之间的关系。出版商(发布者)发布了一份报纸,而读者(订阅者)可以订阅这份报纸,并在报纸发布时收到通知。
使用发布-订阅模式
// 创建一个事件中心对象
const eventCenter = {
// 存储订阅者的列表
subscribers: [],
// 添加订阅者
subscribe: function (callback) {
this.subscribers.push(callback);
},
// 移除订阅者
unsubscribe: function (callback) {
const index = this.subscribers.indexOf(callback);
if (index !== -1) {
this.subscribers.splice(index, 1);
}
},
// 发布消息给所有订阅者
publish: function (message) {
this.subscribers.forEach((callback) => callback(message));
},
};
// 定义一个订阅者函数
function subscriber1(message) {
console.log("Subscriber 1 received:", message);
}
function subscriber2(message) {
console.log("Subscriber 2 received:", message);
}
// 订阅消息
eventCenter.subscribe(subscriber1);
eventCenter.subscribe(subscriber2);
// 发布消息
eventCenter.publish("Hello, world!");
// 输出结果:
// Subscriber 1 received: Hello, world!
// Subscriber 2 received: Hello, world!
不使用发布-订阅模式
// 定义一个订阅者函数
function subscriber1(message) {
console.log("Subscriber 1 received:", message);
}
function subscriber2(message) {
console.log("Subscriber 2 received:", message);
}
// 定义一个发布消息的函数
function publishMessage(message) {
// 直接调用订阅者函数
subscriber1(message);
subscriber2(message);
}
// 发布消息
publishMessage("Hello, world!");
// 输出结果:
// Subscriber 1 received: Hello, world!
// Subscriber 2 received: Hello, world!
观察者模式
观察者模式是一种设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
通俗地说,观察者模式就像是报纸和读者之间的关系。出版商(发布者)发布了一份报纸,而读者(观察者)可以订阅这份报纸,并在报纸发布时收到通知。
使用观察者模式时
// 创建一个发布者对象
const publisher = {
// 存储观察者的列表
observers: [],
// 添加观察者
subscribe: function (observer) {
this.observers.push(observer);
},
// 移除观察者
unsubscribe: function (observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
},
// 通知所有观察者
notify: function (message) {
this.observers.forEach((observer) => observer.update(message));
},
};
// 定义一个观察者函数
function observer1(message) {
console.log("Observer 1 received:", message);
}
function observer2(message) {
console.log("Observer 2 received:", message);
}
// 订阅消息
publisher.subscribe(observer1);
publisher.subscribe(observer2);
// 发布消息
publisher.notify("Hello, world!");
// 输出结果:
// Observer 1 received: Hello, world!
// Observer 2 received: Hello, world!
不使用观察者模式时
// 定义一个订阅者函数
function subscriber1(message) {
console.log("Subscriber 1 received:", message);
}
function subscriber2(message) {
console.log("Subscriber 2 received:", message);
}
// 定义一个发布消息的函数
function publishMessage(message) {
// 直接调用订阅者函数
subscriber1(message);
subscriber2(message);
}
// 发布消息
publishMessage("Hello, world!");
// 输出结果:
// Subscriber 1 received: Hello, world!
// Subscriber 2 received: Hello, world!
观察者模式 和 发布订阅模式 区别
观察者模式和发布-订阅模式在设计上有着显著的不同,主要体现在耦合性、角色定义和通信方式上。
首先,从耦合性方面来看:
- 观察者模式中,观察者和被观察者之间是松耦合的关系,即观察者需要知道被观察者的存在,而被观察者也需要维护一个观察者列表。
- 发布-订阅模式则具有更低的耦合性,发布者和订阅者不需要相互知道对方的存在,它们通过一个经纪人(Broker)或消息中心进行通信。
其次,考虑角色定义的差异:
- 观察者模式通常涉及两个主要角色:观察者和被观察者。
- 发布-订阅模式通常包含三个角色:发布者、订阅者和经纪人(Broker)。
最后,关于通信方式的区别:
- 观察者模式中,当状态变化时,被观察者会直接通知所有注册的观察者。
- 发布-订阅模式中,状态变化时,发布者发送消息到消息中心,而订阅者从消息中心接收消息,无需直接与发布者交互。
综上所述,虽然观察者模式和发布-订阅模式都是为了实现对象间的解耦和动态协作,但发布-订阅模式提供了更为松散的耦合方式,使得系统组件之间的依赖关系更加灵活。