前置知识
构造函数
普通函数
在 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的价格是50const shoes = ProductFactory.createProduct('shoes', 'Running Shoes', 200, 'Nike');console.log(shoes.getDetails()); // 输出:Running Shoes的价格是200const 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的价格是50const shoes = new Shoes('Running Shoes', 200, 'Nike');
console.log(shoes.getDetails()); // 输出:Running Shoes的价格是200const 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)。
最后,关于通信方式的区别:
- 观察者模式中,当状态变化时,被观察者会直接通知所有注册的观察者。
- 发布-订阅模式中,状态变化时,发布者发送消息到消息中心,而订阅者从消息中心接收消息,无需直接与发布者交互。
综上所述,虽然观察者模式和发布-订阅模式都是为了实现对象间的解耦和动态协作,但发布-订阅模式提供了更为松散的耦合方式,使得系统组件之间的依赖关系更加灵活。