设计模式
创建型设计模式
- Constructor 构造器
- Factory 工厂
- Abstract 抽象
- Prototype 原型
- Singleton 单例
- Builder 生成器
- 结构型设计模式
- Decorator 装饰者
- Facade 外观
- Flyweight 享元
- Adapter 适配器
- Proxy 代理
行为设计模式
- Iterator 迭代器
- Mediator 中介者
- Observer 观察者
- Visitor 访问者
Revealing Module 揭示模块
IIFE 实现私有化模块
Javascript
const revealingModule = (function () {
let privateVar = 'xxx';
function privateFn() {
console.log(privateVar);
}
function publicSet(val) {
privateVar = val;
}
function publicGet() {
return privateFn();
}
return {
getVal: publicGet,
setVal: publicSet,
};
})();
单例模式
Javascript
const Singleton = (function () {
let instance;
function init() {
// 私有属性和方法
const prop1 = 'value1';
const prop2 = 'value2';
function method1() {
console.log('method1');
}
function method2() {
console.log('method2');
}
// 公有属性和方法
return {
prop1,
prop2,
method1,
method2,
};
}
return {
// 获取单例实例的方法
getInstance() {
if (!instance) {
instance = init();
}
return instance;
},
};
})();
details
单例模式是一种设计模式,它限制一个类在应用程序中只能有一个实例,并提供全局访问点。单例模式在 JavaScript 中可以通过多种方式实现,以下是几种常见的实现方法:
1. 使用闭包和立即执行函数表达式(IIFE)
这是一个最常见的方式,通过闭包和立即执行函数来实现单例模式:
const Singleton = (function() {
let instance;
function createInstance() {
const object = new Object("I am the instance");
return object;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
2. 使用类
如果你使用 ES6 及更高版本,你可以通过类来实现单例模式:
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
// 其他初始化代码
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
3. 使用模块
在 Node.js 或者其他支持模块的环境中,可以利用模块的特性来实现单例模式。模块在第一次被 require
或 import
时会被缓存,因此可以确保单例实例。
Node.js 环境
// Singleton.js
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
// 其他初始化代码
}
return Singleton.instance;
}
}
module.exports = Singleton;
// main.js
const Singleton = require('./Singleton');
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
ES6 模块环境
// Singleton.js
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
// 其他初始化代码
}
return Singleton.instance;
}
}
export default Singleton;
// main.js
import Singleton from './Singleton.js';
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
4. 使用 Symbol 和 Object.freeze
这种方法可以防止修改单例的实例:
const Singleton = (function() {
const INSTANCE = Symbol();
class Singleton {
constructor(token) {
if (token !== INSTANCE) {
throw new Error('Cannot instantiate directly.');
}
// 初始化代码
}
static getInstance() {
if (!this[INSTANCE]) {
this[INSTANCE] = new Singleton(INSTANCE);
}
return this[INSTANCE];
}
}
Object.freeze(Singleton);
return Singleton;
})();
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
工厂模式
工厂模式指的是定义一个用于创建对象的接口,让子类决定实例化哪一个类。在 JavaScript 中可以使用构造函数实现工厂模式
Javascript
function Factory(type) {
if (type === 'product1') {
return new Product1();
} else if (type === 'product2') {
return new Product2();
}
}
function Product1() {
this.name = 'Product1';
}
function Product2() {
this.name = 'Product2';
}
观察者模式
观察者模式指的是定义对象间一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会收到通知并自动更新。在 JavaScript 中可以使用发布订阅模式实现观察者模式。
Javascript
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);
}
}
notifyObservers() {
for (const observer of this.observers) {
observer.update();
}
}
}
class Observer {
constructor() {}
update() {
console.log('I am updated');
}
}
装饰器模式
装饰器模式指的是在不改变原有对象的基础上,通过对其进行包装或者添加新的功能来扩展其功能。在 JavaScript 中可以使用装饰器实现装饰器模式。
Javascript
function decorate(originalFunc) {
return function () {
console.log('Before originalFunc');
const result = originalFunc.apply(this, arguments);
console.log('After originalFunc');
return result;
};
}
function originalFunc() {
console.log('Inside originalFunc');
}
const decoratedFunc = decorate(originalFunc);
decoratedFunc();
发布-订阅模式
Javascript
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅事件
on(eventName, listener) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(listener);
}
// 取消订阅
off(eventName, listener) {
if (!this.events[eventName]) {
return;
}
const index = this.events[eventName].indexOf(listener);
if (index !== -1) {
this.events[eventName].splice(index, 1);
}
}
// 触发事件
emit(eventName, ...args) {
if (!this.events[eventName]) {
return;
}
// ⚠️⚠️⚠️ 该实现方法有问题,会导致 once 限执行的时候 on 的事件不会触发
// this.events[eventName].forEach(listener => {
// listener.apply(this, args);
// });
// 浅拷贝一份 也可以。禁止遍历 数组的时候 同时修改数组
this.events[eventName].slice().forEach(listener => {
listener.apply(this, args);
});
// 倒序执行 避免 删除数组中 元素后导致 遍历不到
// const len = this.events[eventName].length;
// for (let i = len - 1; i >= 0; i--) {
// this.events[eventName][i].apply(this, args);
// }
}
// 订阅一次性事件
once(eventName, listener) {
const wrapper = (...args) => {
this.off(eventName, wrapper);
listener.apply(this, args);
};
this.on(eventName, wrapper);
}
}
const emitter = new EventEmitter();
emitter.once('hi', arg => {
console.log('once response to', arg);
});
emitter.on('hi', arg => {
console.log('response to ', arg);
});
emitter.emit('hi', 'micheal');
emitter.emit('hi', 'jodan');
特别需要注意 emit 的时候 once 方法会修改原数组,需要拷贝一份,或者 用 index 倒序遍历,不然会出现事件不会触发的情况
Javascript
nodejs 源码中 lib/events.js nodejs 中实现是通过拷贝的方式存储了一份,所以能保证正确结果;
EventEmitter.prototype.emit = function emit(type, ...args) {
const handler = events[type];
const len = handler.length;
const listeners = arrayClone(handler);
for (let i = 0; i < len; ++i) {
const result = listeners[i].apply(this, args);
}
};
function arrayClone(arr) {
// At least since V8 8.3, this implementation is faster than the previous
// which always used a simple for-loop
switch (arr.length) {
case 2:
return [arr[0], arr[1]];
case 3:
return [arr[0], arr[1], arr[2]];
case 4:
return [arr[0], arr[1], arr[2], arr[3]];
case 5:
return [arr[0], arr[1], arr[2], arr[3], arr[4]];
case 6:
return [arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]];
}
return ArrayPrototypeSlice(arr); // 改代码应该是c++ 实现的注入到js
}