深入浅出JavaScript常见设计模式:从原理到实战(2)
深入浅出JavaScript常见设计模式:从原理到实战(2)
本文是深入浅出JavaScript常见设计模式:从原理到实战(1)的续集
设计模式是一种在特定情境下解决软件设计中常见问题的通用方案或模板。在特定的开发场景中使用特定的设计模式,可以提升代码质量,增强代码可读性和可维护性,提高团队开发效率,降低软件设计的复杂度。本文将介绍前端开发中常用的设计模式,讲解他们的含义,核心特性,使用场景及注意事项
一、设计模式核心认知
1.1 什么是设计模式
- 定义:经过验证的代码组织最佳实践方案
- 黄金三角:
- 场景:特定问题的解决方案
- 结构:类/对象的关系拓扑
- 效果:可维护性/扩展性提升
1.2 为什么需要设计模式
- 代码腐化防控:减少面条式代码
- 架构清晰度:提高模块化程度(示例:React Hooks vs Class组件)
- 团队协作:统一代码交流语言
二、三大高频设计模式详解
2.1 代理模式(Proxy)
代理模式(Proxy Pattern)是结构型设计模式的核心实现之一,其核心目标是通过代理对象控制对原始对象的访问,在不修改原始对象的前提下增强其功能。以下是其关键要点:
核心特性
特性 | 说明 |
---|---|
访问控制 | 代理对象控制对原始对象的访问权限 |
功能增强 | 添加缓存、验证、日志等附加功能 |
延迟初始化 | 按需创建高开销对象(虚拟代理) |
接口一致性 | 代理与原始对象实现相同接口,客户端无感知 |
应用场景
-
虚拟代理
- 图片懒加载(延迟加载大图)
- 按需加载模块(Webpack动态导入)
-
保护代理
- API请求权限校验(JWT Token验证)
- 敏感操作审计(删除操作日志记录)
-
缓存代理
- 接口响应缓存(减少重复请求)
- 复杂计算缓存(斐波那契数列记忆化)
-
远程代理
- RPC调用封装(像调用本地方法一样调用远程服务)
- WebSocket通信代理
代码实现(综合缓存+权限校验代理)
class DatabaseService { constructor() { this.data = new Map(); } // 原始方法 query(sql) { console.log(`执行SQL: ${sql}`); return Math.random() * 100; // 模拟查询结果 } // 敏感操作 deleteTable(name) { console.log(`删除表: ${name}`); return true; }
} // 代理类
class DatabaseProxy { constructor(role = 'guest') { this.db = new DatabaseService(); this.cache = new Map(); this.role = role; } // 代理查询方法(添加缓存) query(sql) { if (this.cache.has(sql)) { console.log('返回缓存结果'); return this.cache.get(sql); } const result = this.db.query(sql); this.cache.set(sql, result); return result; } // 代理删除方法(添加权限校验) deleteTable(name) { if (this.role !== 'admin') { throw new Error('权限不足'); } return this.db.deleteTable(name); } // 扩展代理功能(添加日志) auditLog(action) { console.log(`[审计日志] ${new Date().toISOString()} 执行了 ${action}`); }
} // 使用示例
const adminProxy = new DatabaseProxy('admin');
console.log(adminProxy.query('SELECT * FROM users')); // 执行真实查询
console.log(adminProxy.query('SELECT * FROM users')); // 返回缓存结果
adminProxy.deleteTable('logs'); // 成功执行 const guestProxy = new DatabaseProxy();
guestProxy.deleteTable('users'); // 抛出权限错误
实现流程图解
注意事项
-
性能损耗
- 代理链过长会导致调用延迟,解决方案:
// 使用组合代理替代继承链 const proxy = compose(logProxy, cacheProxy, authProxy)(realObject);
-
过度代理
- 避免无意义的代理层,遵循YAGNI原则
// 仅在需要时创建代理 function createProxy(target, needs) { return needs.reduce((obj, feature) => { return feature === 'cache' ? new CacheProxy(obj) : obj; }, target); }
-
与装饰器模式区别
- 代理控制访问,装饰器增强功能
现代框架中的演进
-
Vue3响应式代理
const raw = { count: 0 }; const proxy = new Proxy(raw, { get(target, key) { track(target, key); // 依赖收集 return Reflect.get(...arguments); }, set(target, key, value) { trigger(target, key); // 触发更新 return Reflect.set(...arguments); } });
-
React高阶组件(HOC)
function withLoading(WrappedComponent) { return function Enhanced(props) { const [loading, setLoading] = useState(true); useEffect(() => { setLoading(false); }, []); return loading ? <Spinner /> : <WrappedComponent {...props} />; }; }
-
Node.js HTTP中间件
const proxyMiddleware = (req, res, next) => { const targetUrl = determineTarget(req.url); httpProxy.web(req, res, { target: targetUrl }, next); }; app.use('/api', proxyMiddleware);
模式延伸
-
动态代理
function createDynamicProxy(handler) { return new Proxy({}, { get(target, prop) { return (...args) => handler(prop, args); } }); } const apiProxy = createDynamicProxy((method, params) => { return fetch(`/api/${method}`, { body: JSON.stringify(params) }); }); apiProxy.getUsers({ page: 1 }); // 实际调用 /api/getUsers
-
虚拟化代理
class HeavyResourceProxy { constructor() { this.resource = null; } load() { if (!this.resource) { this.resource = new HeavyResource(); // 按需初始化 } return this.resource; } }
-
代理模式组合
最佳实践示例:实现智能API客户端
class SmartAPIClient { constructor(baseURL) { return new Proxy(this, { get(target, endpoint) { return params => fetch(`${baseURL}/${endpoint}`, { method: 'POST', body: JSON.stringify(params) }) .then(res => res.json()) .catch(err => ({ error: err.message })); } }); }
} // 使用示例
const api = new SmartAPIClient('https://api.example.com');
api.users({ id: 1 }).then(console.log);
api.products({ category: 'books' }).then(console.log);
2.2 工厂模式(Factory)
工厂模式(Factory Pattern)是创建型设计模式的核心实现之一,其核心目标是封装对象创建过程,通过统一接口动态创建不同类型的对象,实现客户端与具体类的解耦。以下是其关键要点:
核心特性
特性 | 说明 |
---|---|
创建封装 | 隐藏对象实例化逻辑,客户端仅关注接口 |
类型扩展 | 新增产品类型无需修改客户端代码 |
多态支持 | 工厂方法返回抽象类型的具体实现 |
条件创建 | 根据输入参数动态创建不同对象 |
应用场景
-
UI组件库
- 根据配置生成不同风格的按钮/表单
- 跨平台组件渲染(Web/Mobile)
-
支付系统
- 根据用户选择创建支付处理器(支付宝/微信/银联)
- 国际化货币处理器
-
游戏开发
- 动态生成NPC角色(敌人/盟友)
- 武器/装备生成系统
-
数据解析
- 根据文件类型选择解析器(JSON/XML/CSV)
- 不同数据源的适配器创建
代码实现(支持抽象工厂的增强版)
// 抽象产品接口
class Button { render() { throw new Error("必须实现render方法"); }
} // 具体产品
class PrimaryButton extends Button { render() { return "<button class='primary'>Submit</button>"; }
} class DangerButton extends Button { render() { return "<button class='danger'>Delete</button>"; }
} // 抽象工厂
class UIFactory { createButton() { throw new Error("必须实现createButton方法"); } createInput() { throw new Error("必须实现createInput方法"); }
} // 具体工厂
class WebUIFactory extends UIFactory { createButton() { return new PrimaryButton(); } createInput() { return "<input type='text'>"; }
} class MobileUIFactory extends UIFactory { createButton() { return new DangerButton(); } createInput() { return "<input type='number'>"; }
} // 客户端代码
function renderUI(platform) { const factory = platform === 'mobile' ? new MobileUIFactory() : new WebUIFactory(); const button = factory.createButton(); const input = factory.createInput(); document.body.innerHTML = `${button.render()} ${input}`;
} // 使用示例
renderUI('web'); // 生成Web风格UI
renderUI('mobile'); // 生成Mobile风格UI
实现流程图解
注意事项
-
避免过度设计
- 简单场景直接使用构造函数
// 只有当需要处理多种类型时使用工厂 class SimpleButton { constructor(type) { this.type = type; } }
-
工厂膨胀问题
- 使用参数化工厂替代多子类
class DynamicUIFactory { constructor(config) { this.config = config; } createButton() { return new this.config.buttonClass(); } }
-
与构造函数对比
工厂模式 构造函数 返回任意对象 必须返回当前类实例 可缓存/复用实例 每次调用创建新实例 支持更复杂的创建逻辑 适合简单初始化场景
现代框架中的演进
-
React组件工厂
function createComponent(type, props) { const components = { Button: <button {...props} />, Input: <input {...props} /> }; return components[type]; } // 使用 <div>{createComponent('Button', { onClick })}</div>
-
Vue插件工厂
const pluginFactory = (options) => ({ install(app) { app.component(options.name, options.component); } }); Vue.use(pluginFactory({ name: 'CustomButton', component: ButtonComponent }));
-
Node.js模块缓存
// Node.js的require机制本质是工厂模式 const express = require('express'); const app = express();
模式延伸
-
工厂方法 vs 抽象工厂
-
与原型模式结合
class PrototypeFactory { constructor(prototypes) { this.prototypes = prototypes; } create(type) { return this.prototypes[type].clone(); } }
-
依赖注入实现
class Container { constructor() { this.factories = new Map(); } register(type, factory) { this.factories.set(type, factory); } resolve(type) { return this.factories.get(type)(); } }
2.3 模块化模式(Module)
模块化模式(Module Pattern)是JavaScript中最基础的组织代码模式,通过闭包和立即执行函数实现作用域隔离,核心目标是封装私有状态,暴露公共接口。以下是其关键要点:
核心特性
特性 | 说明 |
---|---|
命名空间 | 避免全局变量污染 |
私有封装 | 通过闭包保护内部状态 |
接口暴露 | 选择性公开方法与属性 |
依赖管理 | 显式声明模块依赖关系 |
应用场景
-
工具库开发
- Lodash/Underscore等工具函数封装
- 日期处理/数学计算专用模块
-
第三方SDK
- 支付SDK(支付宝/微信支付接口封装)
- 地图SDK(Google Maps API包装)
-
业务模块
- 用户认证模块(登录/注册/权限)
- 购物车管理模块
-
旧代码重构
- 将全局函数重构为模块
- jQuery插件开发
代码实现(支持依赖注入的增强版)
const UserModule = (($, _) => { // 私有变量 let users = []; const API_URL = '/api/users'; // 私有方法 function fetchUsers() { return $.get(API_URL).then(data => { users = _.sortBy(data, 'name'); }); } // 暴露公共接口 return { init() { fetchUsers().then(() => { console.log('用户数据加载完成'); }); }, getUsers() { return [...users]; }, addUser(user) { users.push(user); return $.post(API_URL, user); } };
})(jQuery, _); // 使用示例
UserModule.init();
setTimeout(() => { console.log(UserModule.getUsers());
}, 1000);
实现流程图解
注意事项
-
内存泄漏
- 避免在闭包中保留DOM引用
function createModule() { const element = document.getElementById('app'); // 危险! return { update() { /*...*/ } }; }
-
单元测试困难
- 通过依赖注入解决
const TestableModule = (deps = { fetch: fetch }) => ({ getData() { return deps.fetch('/data'); } });
-
模块通信
通信方式 优点 缺点 全局事件总线 松耦合 难以追踪数据流 直接引用 简单直接 增加耦合度 依赖注入 可测试性好 增加配置复杂度
现代框架中的演进
-
ES6 Modules
// math.js export function add(a, b) { return a + b; } // app.js import { add } from './math.js';
-
Webpack模块联邦
// app1 暴露模块 new ModuleFederationPlugin({ name: "app1", exposes: { "./Button": "./src/Button.js" } }); // app2 使用远程模块 import("app1/Button").then(Button => { ReactDOM.render(<Button />, root); });
-
Node.js CommonJS
// 模块导出 module.exports = { method: () => { /*...*/ } }; // 模块导入 const lib = require('./lib');
模式延伸
-
增强模块模式
const EnhancedModule = (original => ({ ...original, newMethod() { // 扩展功能 } }))(UserModule);
-
沙箱模式
function Sandbox(...deps) { const modules = {}; return { register(name, impl) { modules[name] = impl; }, require(name) { return modules[name]; } }; }
-
动态加载
const DynamicModule = { load(name) { return import(`./modules/${name}.js`); } };
模块化演进史:
最佳实践组合:
// 现代模块化方案
import { factory } from './factory.js';
import { logger } from '@monorepo/utils'; export default (config) => { const module = factory.create(config); logger.debug('Module initialized'); return Object.freeze(module);
};
四、下期预告
总共有23种经典的设计模式,本篇文章介绍了3种前端开发中常用的设计模式,其他设计模式将在下篇文章中继续分享哦,敬请期待~