设计模式的一些笔记
文章目录
- 单例模式(Singleton)
- 观察者模式(Observer)
- 工厂模式(Factory)
- 装饰器模式(Decorator)
- 代理模式(Proxy)
- 模块模式(Module)
- 策略模式(Strategy)
- 日常开发中的使用
- 1. 框架未覆盖的业务场景
- 2. 框架 API 的 “设计模式思维”
- 3. 代码复用与可维护性
单例模式(Singleton)
核心思想:确保一个类 / 对象只有一个实例,并提供全局访问点。
适用场景:全局状态管理(如 Redux 的store)、全局模态框、全局缓存等。
class SingletonModal {constructor() {// 若已存在实例,直接返回if (SingletonModal.instance) return SingletonModal.instance;// 初始化唯一实例this.element = document.createElement('div');this.element.className = 'modal';document.body.appendChild(this.element);// 缓存实例SingletonModal.instance = this;}show(content) {this.element.textContent = content;this.element.style.display = 'block';}hide() {this.element.style.display = 'none';}
}// 测试:多次创建仍为同一实例
const modal1 = new SingletonModal();
const modal2 = new SingletonModal();
console.log(modal1 === modal2); // true(同一实例)
观察者模式(Observer)
核心思想:定义对象间的一对多依赖关系,当一个对象状态变化时,所有依赖它的对象自动收到通知。
适用场景:事件监听、状态管理(如 Vue 响应式、React 的useEffect)、发布 - 订阅系统。
class EventEmitter {constructor() {this.events = {}; // 存储事件订阅:{ 'eventName': [callback1, callback2] }}// 订阅事件on(eventName, callback) {if (!this.events[eventName]) this.events[eventName] = [];this.events[eventName].push(callback);}// 发布事件(通知所有订阅者)emit(eventName, ...args) {if (this.events[eventName]) {this.events[eventName].forEach(callback => callback(...args));}}// 取消订阅off(eventName, callback) {if (this.events[eventName]) {this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);}}
}// 测试
const emitter = new EventEmitter();
const log = (msg) => console.log('收到消息:', msg);emitter.on('message', log);
emitter.emit('message', 'Hello 观察者模式'); // 输出:收到消息:Hello 观察者模式
emitter.off('message', log);
emitter.emit('message', '再发一次'); // 无输出(已取消订阅)
工厂模式(Factory)
核心思想:封装对象的创建逻辑,通过 “工厂” 统一创建不同类型的对象,隐藏创建细节。
适用场景:组件库(根据类型创建不同组件)、表单元素生成、多类型数据解析。
// 定义不同类型的组件
class Button { render() { return '<button>按钮</button>'; } }
class Input { render() { return '<input type="text">'; } }
class Select { render() { return '<select></select>'; } }// 组件工厂:根据类型创建对应组件
class ComponentFactory {static createComponent(type) {switch (type) {case 'button': return new Button();case 'input': return new Input();case 'select': return new Select();default: throw new Error('未知组件类型');}}
}// 测试:通过工厂创建组件
const button = ComponentFactory.createComponent('button');
console.log(button.render()); // <button>按钮</button>
装饰器模式(Decorator)
核心思想:动态给对象添加额外功能,不改变其原有结构,实现 “功能扩展” 而非 “继承”。
适用场景:React 高阶组件(HOC)、日志增强、权限控制、性能埋点。
// 基础功能:普通按钮
class Button {click() {console.log('按钮被点击');}
}// 装饰器1:添加日志功能
function withLog(Component) {return class extends Component {click() {console.log('【日志】记录点击时间:', new Date()); // 新增功能super.click(); // 保留原功能}};
}// 装饰器2:添加权限校验
function withAuth(Component) {return class extends Component {click() {const hasPermission = true; // 实际项目中从权限系统获取if (hasPermission) {super.click(); // 有权限才执行原功能} else {console.log('【权限】无点击权限');}}};
}// 应用装饰器
const EnhancedButton = withAuth(withLog(Button));
new EnhancedButton().click();
// 输出:
// 【日志】记录点击时间:XXX
// 按钮被点击
代理模式(Proxy)
核心思想:通过一个 “代理对象” 控制对原对象的访问,可在访问前后添加额外逻辑(如拦截、缓存、验证)。
适用场景:Vue3 响应式(Proxy实现数据劫持)、图片懒加载、请求拦截(Axios 拦截器)。
// 原对象:图片加载
class ImageLoader {load(url) {const img = new Image();img.src = url;console.log('加载图片:', url);}
}// 代理对象:实现图片懒加载(滚动到可视区才加载)
class LazyLoadProxy {constructor(loader) {this.loader = loader;this.urls = []; // 缓存未加载的图片this.bindScroll(); // 监听滚动事件}// 代理load方法:先缓存,滚动时判断是否加载load(url) {this.urls.push(url);this.checkLoad(); // 立即检查一次}// 检查是否需要加载(简化版:假设可视区高度为500px)checkLoad() {const scrollTop = document.documentElement.scrollTop;this.urls = this.urls.filter(url => {if (scrollTop < 500) { // 模拟:滚动到可视区this.loader.load(url);return false; // 已加载,从缓存移除}return true; // 未加载,保留缓存});}bindScroll() {window.addEventListener('scroll', () => this.checkLoad());}
}// 测试:通过代理实现懒加载
const loader = new ImageLoader();
const proxy = new LazyLoadProxy(loader);
proxy.load('image1.jpg'); // 初始不加载(假设滚动条在顶部)
proxy.load('image2.jpg');
// 当滚动时,符合条件的图片会被加载
模块模式(Module)
核心思想:通过闭包创建私有变量和方法,只暴露必要的接口,实现 “信息隐藏”。
适用场景:ES6 模块出现前的模块化开发(如 jQuery 源码)、工具类封装。
const ToolModule = (function() {// 私有变量(外部无法直接访问)const privateKey = 'secret';// 私有方法function privateEncrypt(str) {return str + privateKey;}// 暴露公共接口return {encrypt: (str) => privateEncrypt(str),decrypt: (str) => str.replace(privateKey, '')};
})();// 测试:只能访问暴露的方法
console.log(ToolModule.encrypt('test')); // testsecret
console.log(ToolModule.decrypt('testsecret')); // test
console.log(ToolModule.privateKey); // undefined(私有变量不可访问)
策略模式(Strategy)
核心思想:定义一系列算法(策略),将其封装为独立对象,可动态切换使用。
适用场景:表单验证(不同字段用不同验证规则)、支付方式选择、排序算法切换。
// 定义验证策略(不同算法)
const validators = {required: (value) => value.trim() !== '' || '必填项',email: (value) => /^[\w-]+@([\w-]+\.)+[\w-]+$/.test(value) || '邮箱格式错误',minLength: (min) => (value) => value.length >= min || `至少${min}个字符`
};// 验证器:根据策略执行验证
class Validator {constructor() {this.rules = []; // 存储字段的验证规则}// 添加验证规则(策略)addRule(field, strategy, ...args) {const ruleFunc = typeof strategy === 'function' ? strategy(...args) : validators[strategy];this.rules.push({ field, ruleFunc });}// 执行验证validate(data) {for (const { field, ruleFunc } of this.rules) {const error = ruleFunc(data[field]);if (error !== true) return { field, error }; // 返回第一个错误}return null; // 无错误}
}// 测试:验证表单
const validator = new Validator();
validator.addRule('username', 'required');
validator.addRule('email', 'email');
validator.addRule('password', 'minLength', 6);const formData = { username: '', email: 'invalid', password: '123' };
console.log(validator.validate(formData));
// 输出:{ field: 'username', error: '必填项' }(第一个错误)
前端框架中的设计模式
React:使用观察者模式(状态更新触发重新渲染)、装饰器模式(HOC)、组合模式(组件嵌套)。
Vue:使用代理模式(Proxy实现响应式)、观察者模式(watch/computed)、工厂模式(Vue.component)。
Redux:使用单例模式(store唯一)、观察者模式(subscribe订阅状态变化)。
选择设计模式的核心原则:“解决问题” 而非 “为了用而用”。简单场景无需过度设计,复杂场景(如组件库、状态管理)合理应用模式可显著提升代码质量。
日常开发中的使用
虽然前端框架(如 React、Vue)确实内置了很多设计模式(比如 React 的组件树用了组合模式,Vue 的响应式用了代理模式),但实际业务场景中,我们仍需要主动运用设计模式解决具体问题。
1. 框架未覆盖的业务场景
复杂表单验证:框架不会帮你实现 “手机号 / 邮箱 / 密码” 的多规则验证,但用策略模式可以轻松封装不同验证规则,实现 “规则可配置、可复用”。
// 策略模式封装验证规则(业务场景)
const validators = {phone: (val) => /^1[3-9]\d{9}$/.test(val) || '手机号格式错误',email: (val) => /^[\w-]+@\w+\.\w+$/.test(val) || '邮箱格式错误',// 更多规则...
};
// 通用验证函数(无需关心具体规则)
const validate = (type, val) => validators[type](val);
全局状态管理(非框架场景):如果项目不使用 Redux/Vuex,但需要跨组件共享状态(如用户信息、全局配置),用单例模式 + 观察者模式可以快速实现简易状态管理:
// 单例+观察者实现简易状态管理(业务场景)
const store = (() => {let state = { user: null };const listeners = [];return {getState: () => state,setState: (newState) => {state = { ...state, ...newState };listeners.forEach(fn => fn(state)); // 通知所有订阅者},subscribe: (fn) => listeners.push(fn)};
})();
2. 框架 API 的 “设计模式思维”
比如:
用 React 的useEffect时,本质是在使用观察者模式(监听状态变化并执行副作用);
封装 Vue 组件时,用props传递配置、emit触发事件,本质是策略模式(组件行为由外部策略配置);
开发 React 高阶组件(HOC)时,其实是在应用装饰器模式(给组件动态添加功能)。
3. 代码复用与可维护性
日常开发中,很多问题可以通过设计模式简化:
开发 “弹窗组件” 时,用单例模式避免重复创建 DOM(全局唯一弹窗);
开发 “表格组件” 时,用工厂模式根据列类型(文本、按钮、选择框)动态创建单元格;
开发 “权限控制” 时,用代理模式拦截组件渲染(无权限时显示占位内容)。
//代理模式实现权限控制的示例
// 1. 定义一个需要权限控制的原组件(示例:用户管理组件)
class UserManager {render() {return `<div class="user-manager"><h3>用户管理</h3><button>添加用户</button><button>删除用户</button></div>`;}
}// 2. 权限代理组件(核心:控制对原组件的访问)
class PermissionProxy {/*** 创建权限代理* @param {Object} component - 被代理的原组件* @param {string} requiredPermission - 所需权限标识(如 'user:manage')* @param {Array} userPermissions - 用户拥有的权限列表*/constructor(component, requiredPermission, userPermissions) {this.component = component; // 原组件this.requiredPermission = requiredPermission; // 访问该组件需要的权限this.userPermissions = userPermissions; // 用户实际拥有的权限}// 代理渲染方法:拦截原组件的渲染,加入权限判断render() {// 检查用户是否有权限const hasPermission = this.userPermissions.includes(this.requiredPermission);if (hasPermission) {// 有权限:渲染原组件return this.component.render();} else {// 无权限:显示占位内容(提示无权限)return `<div class="permission-denied"><p>🔒 您没有 "${this.requiredPermission}" 权限,无法访问该功能</p></div>`;}}
}// 3. 使用示例
// 模拟用户权限(实际项目中从后端获取)
const userPermissions = ['user:view', 'post:manage']; // 该用户有"查看用户"和"管理帖子"权限// 创建原组件
const rawUserManager = new UserManager();// 用代理包装原组件,要求"user:manage"权限
const userManagerWithProxy = new PermissionProxy(rawUserManager, 'user:manage', // 访问用户管理需要的权限userPermissions
);// 渲染组件(实际项目中会插入到DOM)
console.log('渲染结果:');
console.log(userManagerWithProxy.render());
// 输出:<div class="permission-denied">...</div>(因为用户没有'user:manage'权限)// 扩展:代理模式的灵活性(无需修改原组件,可动态切换权限)
setTimeout(() => {console.log('\n3秒后权限更新:');// 动态添加权限(模拟权限变更场景)userPermissions.push('user:manage');console.log(userManagerWithProxy.render());// 输出:原UserManager组件的内容(有权限了)
}, 3000);