常用设计模式总结
前端开发中常用的设计模式及使用场景
问题
常用设计模式有哪些?分别在什么场景下使用?
解答
1. 单例模式
确保一个类只有一个实例。
使用场景:全局状态管理、弹窗组件、登录框
// 单例模式
class Singleton {
static instance = null;
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
const a = Singleton.getInstance();
const b = Singleton.getInstance();
console.log(a === b); // true
// 实际应用:全局 Modal
const Modal = (function() {
let instance = null;
return function() {
if (!instance) {
instance = document.createElement('div');
instance.className = 'modal';
document.body.appendChild(instance);
}
return instance;
};
})();
2. 工厂模式
通过工厂函数创建对象,隐藏创建细节。
使用场景:根据条件创建不同类型的对象
// 简单工厂
function createUser(role) {
const roles = {
admin: { name: 'Admin', permissions: ['read', 'write', 'delete'] },
editor: { name: 'Editor', permissions: ['read', 'write'] },
guest: { name: 'Guest', permissions: ['read'] }
};
return roles[role] || roles.guest;
}
const admin = createUser('admin');
const guest = createUser('guest');
// React 中的工厂模式
function createComponent(type, props) {
const components = {
button: Button,
input: Input,
select: Select
};
const Component = components[type];
return <Component {...props} />;
}
3. 观察者模式
对象间一对多的依赖关系,状态变化时通知所有依赖者。
使用场景:事件系统、数据绑定、消息通知
// 观察者模式
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
return this;
}
// 发布
emit(event, ...args) {
const callbacks = this.events[event];
if (callbacks) {
callbacks.forEach(cb => cb(...args));
}
return this;
}
// 取消订阅
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
return this;
}
}
// 使用
const emitter = new EventEmitter();
emitter.on('login', user => console.log(`${user} logged in`));
emitter.emit('login', 'John'); // John logged in
4. 发布订阅模式
与观察者模式类似,但通过事件中心解耦。
使用场景:跨组件通信、微前端通信
// 发布订阅(有事件中心)
class PubSub {
static events = {};
static subscribe(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// 返回取消订阅函数
return () => this.unsubscribe(event, callback);
}
static publish(event, data) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(data));
}
}
static unsubscribe(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
// 组件 A 订阅
const unsubscribe = PubSub.subscribe('update', data => {
console.log('Received:', data);
});
// 组件 B 发布
PubSub.publish('update', { id: 1, name: 'test' });
5. 策略模式
定义一系列算法,封装起来可互相替换。
使用场景:表单验证、价格计算、权限判断
// 策略模式 - 表单验证
const strategies = {
required: value => value.trim() !== '' || '必填项',
minLength: (value, len) => value.length >= len || `最少${len}个字符`,
maxLength: (value, len) => value.length <= len || `最多${len}个字符`,
email: value => /^\S+@\S+\.\S+$/.test(value) || '邮箱格式错误',
phone: value => /^1[3-9]\d{9}$/.test(value) || '手机号格式错误'
};
function validate(value, rules) {
for (const rule of rules) {
const [name, ...args] = rule.split(':');
const result = strategies[name](value, ...args);
if (result !== true) return result;
}
return true;
}
// 使用
validate('', ['required']); // '必填项'
validate('abc', ['required', 'minLength:6']); // '最少6个字符'
validate('test@example.com', ['required', 'email']); // true
6. 代理模式
为对象提供代理,控制对原对象的访问。
使用场景:懒加载、缓存、权限控制、数据劫持
// 代理模式 - 缓存代理
function createCachedRequest(request) {
const cache = new Map();
return async function(url) {
if (cache.has(url)) {
console.log('From cache');
return cache.get(url);
}
const result = await request(url);
cache.set(url, result);
return result;
};
}
const cachedFetch = createCachedRequest(fetch);
// Vue 3 响应式原理 - Proxy
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log(`get ${key}`);
return target[key];
},
set(target, key, value) {
console.log(`set ${key} = ${value}`);
target[key] = value;
return true;
}
});
}
const state = reactive({ count: 0 });
state.count; // get count
state.count = 1; // set count = 1
7. 装饰器模式
动态给对象添加额外职责。
使用场景:日志记录、性能统计、权限校验
// 装饰器模式 - 函数装饰
function withLog(fn) {
return function(...args) {
console.log(`调用 ${fn.name},参数:`, args);
const result = fn.apply(this, args);
console.log(`返回:`, result);
return result;
};
}
function add(a, b) {
return a + b;
}
const loggedAdd = withLog(add);
loggedAdd(1, 2);
// 调用 add,参数: [1, 2]
// 返回: 3
// TypeScript 装饰器
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`${name} called`);
return original.apply(this, args);
};
return descriptor;
}
class Calculator {
@log
add(a, b) {
return a + b;
}
}
8. 适配器模式
将一个接口转换成另一个接口。
使用场景:接口兼容、数据格式转换
// 适配器模式 - API 数据适配
// 后端返回的数据
const apiData = {
user_name: 'john',
user_age: 25,
created_at: '2024-01-01'
};
// 适配器:转换为前端需要的格式
function adaptUser(data) {
return {
name: data.user_name,
age: data.user_age,
createdAt: new Date(data.created_at)
};
}
const user = adaptUser(apiData);
// { name: 'john', age: 25, createdAt: Date }
// 适配不同的请求库
class HttpAdapter {
constructor(httpLib) {
this.http = httpLib;
}
get(url) {
// 统一接口,内部适配不同库
if (this.http.request) {
return this.http.request({ url, method: 'GET' });
}
return this.http.get(url);
}
}
关键点
- 单例模式:全局唯一实例,适用于弹窗、Store
- 工厂模式:封装创建逻辑,根据条件返回不同对象
- 观察者/发布订阅:解耦事件发送者和接收者,Vue/React 事件系统
- 策略模式:算法封装可替换,消除 if-else,适用于表单验证
- 代理模式:控制访问,Vue 3 响应式、缓存、懒加载
- 装饰器模式:不修改原对象增强功能,日志、权限
- 适配器模式:接口转换,API 数据格式化
目录