JS-设计模式 | 青训营
1. 单例模式
单例模式就是一个类只能创建单个对象(保证一个类,仅有一个实例),也就是说一对一,这个类是访问实例的唯一,也可直接访问,不需要实例化该类的对象。单例模式也属于一种创建型模式。
1.1 静态方法版
class Singleton {
action() {
console.log("单例对象");
}
static getInstance() {
// 判断是否已经创建过1个实例
if (!Singleton.instance) {
// 若这个唯一的实例不存在,那么先创建它
Singleton.instance = new Singleton();
}
// 如果实例已经存在,则直接返回
return Singleton.instance;
}
}
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
// true
s1 === s2;
1.2 构造函数
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
}
}
s1 = new Singleton();
s2 = new Singleton();
// true
console.log(s1 === s2);
1.3 闭包 + IIFE
class Singleton {
action() {
console.log("单例对象");
}
static getInstance = (function () {
let instance = null;
return function () {
if (!instance) {
instance = new Singleton();
}
return instance;
};
})();
}
const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2);
2. 工厂模式
工厂模式其实就是将创建对象的过程单独封装。
工厂模式会根据不同的输入返回所要求的实例,将对象的创建与实现分开来。用户提供参数,工厂负责返回相应的产品,其中的逻辑处理用户并不关心。
先构建一个人员的对象
const worker = {
name: '123',
age: 12,
gender: 'male',
// ......
}
上面的worker只能对应某一个人员,面对现实中属性各异的人员,一个一个创建显然不科学,也不难想到利用不同构造函数来创建实例。
function Coder(name, age) { //录入每个程序员的信息
this.name = name;
this.age = age;
this.career = 'coder';
this.work = ['写bug', '改bug', '996'];
}
function ProductManager(name, age) { //录入每个产品的信息
this.name = name;
this.age = age;
this.career = 'product manager';
this.work = ['开会', '画图', '监督'];
}
const coder1 = new Coder('test1', 19);
const manager1 = new ProductManager('test2', 32);
以上通过调用不同的构造函数来获取不同的实例,但可以通过一个统一的工厂函数来实现。
function Factory(name, age, career) {
switch (career) {
case 'coder':
return new Coder(name, age);
break;
case 'product manager':
return new ProductManager(name, age);
break;
}
}
const coder1 = new Factory('test1', 19, 'code');
const manager1 = new Factory('test2', 32, 'product manager');
继续改进,以上代码对不同类之间的共性整合太少,多种不同人员需要调用多种不同的构造函数来获取实例,我们对构造函数本身进行改进。
function User(name, age, career, work) {
this.name = name;
this.age = age;
this.career = career;
this.work = work;
}
function Factory(name, age, career) {
switch (career) {
case 'coder':
work = ['写bug', '改bug', '996'];
break;
case 'product manager':
work = ['开会', '画图', '监督'];
break;
}
return new User(name, age, career, work);
}
3. 观察者模式
观察者模式是一种对象间的一对多依赖关系,当一个对象(主题)的状态发生变化时,其所有依赖对象(观察者)都会得到通知并自动更新。
涉及到两方:观察者 和 被观察者
// 被观察者
class ObservedTarget {
constructor() {
this.observers = []; //用于存储所有的观察者
}
addObserver(...observer) {
this.observers.push(...observer); //添加观察者
}
notifyObserver(...args) {
// 遍历观察者列表,调用观察者的update方法
this.observers.forEach((item) => {
item.update(...args);
});
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name;
}
update(...args) {
let content = [...args];
console.log(`${this.name}接收到目标对象更新的状态是:${content}`);
}
}
在以上的代码中,定义了两个类,分别代表被观察者和观察者,被观察者维护一个观察者的列表,在有变化需要通知观察者时,调用notify方法,实质会遍历观察者列表,并调用其update方法
// 创建多个观察者
let observer1 = new Observer("observer1");
let observer2 = new Observer("observer2");
let observer3 = new Observer("observer3");
// 把观察者本身依附在目标对象上
let observerTarget = new ObservedTarget();
observerTarget.addObserver(observer1, observer2, observer3); //直接关联
// 当目标对象更新内容时,通知所有的观察者
observerTarget.notifyObserver("来自被观察者", "我已更新");
//observer1接收到目标对象更新的状态是:来自被观察者,我已更新
//observer2接收到目标对象更新的状态是:来自被观察者,我已更新
//observer3接收到目标对象更新的状态是:来自被观察者,我已更新
4. 发布订阅模式
发布订阅模式中,发布者与订阅者并不直接关联,而是通过一个中介进行互动,该中介被称为事件总线,订阅者向事件总线订阅感兴趣的内容,发布者向事件总线发布自己的内容,事件总线根据内容向已订阅的订阅者进行通知操作。
// 发布订阅
const eventHub = {
map: {},
// 订阅事件
on: (event, fn) => {
eventHub.map[event] = eventHub.map[event] || [];
eventHub.map[event].push(fn);
},
// 取消订阅事件
off: (event, fn) => {
const q = eventHub.map[event];
if (!q) return;
// 此处targetFn用于处理once的回调函数
const index = q.findIndex((item) => item === fn || item.targetFn === fn);
if (index < 0) {
return;
}
q.splice(index, 1);
},
// 发布事件
emit: (event, ...args) => {
const q = eventHub.map[event];
if (!q) return;
q.map((fn) => fn.call(null, ...args));
return undefined;
},
// 订阅事件,回调只执行一次
once: (event, fn) => {
const on = (...args) => {
eventHub.off(event, fn);
fn.call(null, ...args);
};
// 用于在off时,判断是否为原有的回调函数
on.targetFn = fn;
eventHub.on(event, on);
},
};
以上代码实现了一个 EventHub,主要包含以下几个方法:
- on:用于订阅事件
- off:用于取消订阅事件
- once:用于订阅事件,但回调只执行一次
- emit:用于发布事件
eventHub.on("click", console.log);
eventHub.on("click", console.error);
const logReverse = (s) => console.log(s.split("").reverse().join(""));
eventHub.once("click", logReverse);
setTimeout(() => {
eventHub.emit("click", "123");
}, 3000);
setTimeout(() => {
eventHub.emit("click", "456");
}, 4000);
运行结果如图:
可以看到 logReverse
只执行了一次,满足需求。