前言
我们都知道,设计模式,是在软件设计开发过程中,针对特定问题或场景 的较优解决方案。它可以帮助我们遇到相似的问题、场景时,能够快速找到更优的方式解决。而在日常面试中我们常遇见的就是:单例模式和发布订阅模式了。
单例模式
首先让我们来了解一下单例模式,单例模式是一种设计模式,用于确保类只有一个实例,并提供了全局访问点。这意味着在应用程序的生命周期内,无论何时需要该类的实例,都只会返回同一个实例。在我的日常使用中,需要用到单例模式的时候最常见的就是 vuex
了。单例模式通常在以下情况下被使用:
- 资源共享:当多个组件需要访问相同的资源时,单例模式可以确保只有一个实例被创建和共享,避免资源的重复创建和浪费。
- 全局状态管理:在需要全局状态管理的应用程序中,单例模式可以提供一个统一的访问点来管理应用程序的状态,使得状态的修改和访问更加方便和可控。
- 配置对象:当应用程序需要一个全局配置对象时,单例模式可以确保只有一个配置对象被创建,并且可以在整个应用程序中被访问和修改。
- 日志记录器:在需要记录应用程序日志的情况下,单例模式可以确保只有一个日志记录器被创建,并且可以在整个应用程序中被访问,以便统一管理日志输出。
- 数据库连接池:在需要管理数据库连接的应用程序中,单例模式可以确保只有一个数据库连接池被创建,并且可以在整个应用程序中被共享和复用,提高数据库访问的效率和性能。
那么我们要如何来实现一个单例模式呢?我们可以通过使用闭包和构造函数来实现单例模式。
实现方法
闭包实现
js
function Storage() {
this.name = '张三';
}
const Helper = (function () {
let instance = null;
return function () {
if (!instance) {
instance = new Storage();
}
return instance;
};
})();
let p1 = Helper();
let p2 = Helper();
console.log(p1 === p2); // 输出 true,说明只有一个实例被创建
在以上代码中,我们通过构造了一个 Helper
函数返回一个闭包,当我们需要创建实例对象时,就可以调用这个函数,因为我们在函数内部定义了 instane
作为标记,当第一次创建实例时,就会创建一个新的 Storage
实例赋给 instance
,然后返回这个 instance
;当已经创建了实例时,instance
的值将不会是 null
了,因此他也就不会再创建一个新的实例了。这样可以确保只有一个 Storage
实例被创建和共享。
构造函数实现
js
class Person {
constructor(name) {
this.name = name;
}
static getInfo(name) {
if (!this.instance) {
this.instance = new Person(name);
}
return this.instance;
}
}
let p1 = Person.getInfo('张三');
let p2 = Person.getInfo('李四');
console.log(p1 === p2); // 输出 true,说明只有一个实例被创建
在以上代码中,我们在 Person
函数中创建了一个静态方法 getInfo
,在 getInfo
中如果在之前我们没有创建一个实例的话,那这个构造函数身上也就不会有 instance
这个属性,如果有的话我们只需要直接返回这个属性就行了。这样,无论调用 getInfo
多少次,都只会返回同一个实例,实现了单例模式的效果。
发布订阅
首先让我们来了解一下发布订阅是什么,发布订阅模式就是用于实现对象之间一种一对多的依赖关系,其中只存在一个发布者,但是可以有多个订阅者。发布者负责发布事件,而订阅者则订阅这些事件,并在事件发生时被通知到。也就是说,当对象身上发生改变时,其身上所依赖的对象也要相应的发生改变。发布-订阅模式通常在以下情况下被使用到:
- 松耦合的组件通信:当需要实现松耦合的组件通信时,发布-订阅模式非常有用。组件之间不需要直接引用彼此,而是通过事件总线进行通信,从而降低了它们之间的依赖关系,提高了组件的可复用性和可维护性。
- 跨组件通信:当需要在多个组件之间进行通信时,发布-订阅模式可以帮助实现跨组件的通信。通过订阅感兴趣的事件,并在事件发生时执行相应的操作,可以方便地实现组件之间的数据传递和状态同步。
- 解耦异步操作:当需要解耦异步操作时,发布-订阅模式可以提供一种方便的方式来处理异步事件。例如,可以订阅异步操作的成功或失败事件,并在事件发生时执行相应的操作,而不需要在异步操作的回调函数中直接处理业务逻辑。
- 全局事件管理:当需要管理全局事件时,发布-订阅模式可以帮助实现全局事件的管理和分发。通过使用一个事件总线来管理所有的事件,并在需要时发布和订阅事件,可以方便地实现全局事件的处理和分发。
- 插件和模块化开发:在插件和模块化开发中,发布-订阅模式可以提供一种灵活的扩展机制。插件可以通过订阅特定的事件来扩展应用程序的功能,而不需要直接修改应用程序的源代码,从而实现了插件和模块之间的解耦。
通常来说,我们是使用事件总线来实现发布订阅者模式的。事件总线在这个过程中充当着中介,负责管理事件的发布和订阅。下面我来带大家实现一个发布订阅模式。
实现方法
js
class EventEmitter {
constructor() {
this.event = {};
}
on(Event, fn) {
if (!this.event[Event]) {
this.event[Event] = [];
}
this.event[Event].push(fn);
}
emit(Event) {
this.event[Event].forEach((fn) => {
fn();
});
}
off(Event, fn) {
console.log(this.event[Event]);
if (this.event[Event].indexOf(fn) !== -1) {
this.event[Event].splice(this.event[Event].indexOf(fn), 1);
}
}
}
let emiter = new EventEmitter();
function A() {
console.log("A");
}
emiter.on("click", A);
emiter.off("click", A);
function B() {
console.log("B");
}
emiter.on("click", B);
emiter.emit("click");
在以上代码中,因为我们需要一个事件总线来充当中介,因此定义了 EventEmitter
函数,为了应对有多个事件的情况,我们定义了一个对象 event
来存储这些事件,并使用对象名 eventName
来作为key,因为一对多的关系,所以需要将其设置为数组类型。
然后它有三个方法:on
、emit
和 off
。
markdown
- `on(Event, fn)`:订阅事件,当事件被触发时执行指定的回调函数。
- `emit(Event)`:触发事件,执行该事件对应的所有回调函数。
- `off(Event, fn)`:取消订阅事件,从事件的回调函数列表中移除指定的回调函数。
-
订阅事件:在调用
on
方法时,会将事件名和对应的回调函数存储在event
对象中。如果之前没有该事件名对应的回调函数列表,则创建一个新的数组,并将回调函数存储在其中。 -
发布事件:在调用
emit
方法时,会根据事件名找到对应的回调函数列表,并依次执行其中的每个回调函数。 -
取消订阅事件:在调用
off
方法时,首先判断是否存在该事件名对应的回调函数列表,如果存在,则在列表中找到指定的回调函数,并将其从列表中移除。
这样我们就实现了一个简单的发布订阅模式了。
总结
单例模式:
单例模式是一种常见的设计模式,它能够确保一个类只有一个实例,并提供了全局访问点。在实际应用中,单例模式特别适用于需要管理全局状态或资源的场景。通过单例模式,我们可以避免资源的重复创建和浪费,同时提供一个统一的访问点来管理应用程序的状态。因此在日常开发中,我经常会使用单例模式来管理全局的配置对象、日志记录器或数据库连接池等资源,以提高代码的效率和性能。
发布-订阅模式:
发布-订阅模式是一种常见的设计模式,它用于实现对象之间的一对多的依赖关系。通过发布-订阅模式,我们可以实现松耦合的组件通信、跨组件通信以及解耦异步操作等功能。在实际应用中,发布-订阅模式特别适用于需要实现组件之间的解耦或跨组件通信的场景。通过订阅感兴趣的事件,并在事件发生时执行相应的操作,我们可以方便地实现组件之间的数据传递和状态同步,从而提高代码的可维护性和可扩展性。