当前位置: 首页 > news >正文

深入浅出JavaScript常见设计模式:从原理到实战(2)


深入浅出JavaScript常见设计模式:从原理到实战(2)

本文是深入浅出JavaScript常见设计模式:从原理到实战(1)的续集

   设计模式是一种在特定情境下解决软件设计中常见问题的通用方案或模板。在特定的开发场景中使用特定的设计模式,可以提升代码质量,增强代码可读性和可维护性,提高团队开发效率,降低软件设计的复杂度。本文将介绍前端开发中常用的设计模式,讲解他们的含义,核心特性,使用场景及注意事项


一、设计模式核心认知

1.1 什么是设计模式

  • 定义:经过验证的代码组织最佳实践方案
  • 黄金三角
    • 场景:特定问题的解决方案
    • 结构:类/对象的关系拓扑
    • 效果:可维护性/扩展性提升

1.2 为什么需要设计模式

  • 代码腐化防控:减少面条式代码
  • 架构清晰度:提高模块化程度(示例:React Hooks vs Class组件)
  • 团队协作:统一代码交流语言

二、三大高频设计模式详解

2.1 代理模式(Proxy)

  代理模式(Proxy Pattern)是结构型设计模式的核心实现之一,其核心目标是通过代理对象控制对原始对象的访问,在不修改原始对象的前提下增强其功能。以下是其关键要点:


核心特性
特性说明
访问控制代理对象控制对原始对象的访问权限
功能增强添加缓存、验证、日志等附加功能
延迟初始化按需创建高开销对象(虚拟代理)
接口一致性代理与原始对象实现相同接口,客户端无感知

应用场景
  1. 虚拟代理

    • 图片懒加载(延迟加载大图)
    • 按需加载模块(Webpack动态导入)
  2. 保护代理

    • API请求权限校验(JWT Token验证)
    • 敏感操作审计(删除操作日志记录)
  3. 缓存代理

    • 接口响应缓存(减少重复请求)
    • 复杂计算缓存(斐波那契数列记忆化)
  4. 远程代理

    • 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'); // 抛出权限错误  

实现流程图解

在这里插入图片描述

注意事项
  1. 性能损耗

    • 代理链过长会导致调用延迟,解决方案:
    // 使用组合代理替代继承链  
    const proxy = compose(logProxy, cacheProxy, authProxy)(realObject);  
    
  2. 过度代理

    • 避免无意义的代理层,遵循YAGNI原则
    // 仅在需要时创建代理  
    function createProxy(target, needs) {  return needs.reduce((obj, feature) => {  return feature === 'cache' ? new CacheProxy(obj) : obj;  }, target);  
    }  
    
  3. 与装饰器模式区别

    • 代理控制访问,装饰器增强功能
    代理模式
    访问控制
    装饰器模式
    功能叠加

现代框架中的演进
  1. 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);  }  
    });  
    
  2. React高阶组件(HOC)

    function withLoading(WrappedComponent) {  return function Enhanced(props) {  const [loading, setLoading] = useState(true);  useEffect(() => {  setLoading(false);  }, []);  return loading ? <Spinner /> : <WrappedComponent {...props} />;  };  
    }  
    
  3. Node.js HTTP中间件

    const proxyMiddleware = (req, res, next) => {  const targetUrl = determineTarget(req.url);  httpProxy.web(req, res, { target: targetUrl }, next);  
    };  app.use('/api', proxyMiddleware);  
    

模式延伸
  1. 动态代理

    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  
    
  2. 虚拟化代理

    class HeavyResourceProxy {  constructor() {  this.resource = null;  }  load() {  if (!this.resource) {  this.resource = new HeavyResource(); // 按需初始化  }  return this.resource;  }  
    }  
    
  3. 代理模式组合

    缓存命中
    Client
    CacheProxy
    AuthProxy
    RealObject

最佳实践示例:实现智能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)是创建型设计模式的核心实现之一,其核心目标是封装对象创建过程,通过统一接口动态创建不同类型的对象,实现客户端与具体类的解耦。以下是其关键要点:


核心特性
特性说明
创建封装隐藏对象实例化逻辑,客户端仅关注接口
类型扩展新增产品类型无需修改客户端代码
多态支持工厂方法返回抽象类型的具体实现
条件创建根据输入参数动态创建不同对象

应用场景
  1. UI组件库

    • 根据配置生成不同风格的按钮/表单
    • 跨平台组件渲染(Web/Mobile)
  2. 支付系统

    • 根据用户选择创建支付处理器(支付宝/微信/银联)
    • 国际化货币处理器
  3. 游戏开发

    • 动态生成NPC角色(敌人/盟友)
    • 武器/装备生成系统
  4. 数据解析

    • 根据文件类型选择解析器(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  

实现流程图解

在这里插入图片描述


注意事项
  1. 避免过度设计

    • 简单场景直接使用构造函数
    // 只有当需要处理多种类型时使用工厂  
    class SimpleButton {  constructor(type) {  this.type = type;  }  
    }  
    
  2. 工厂膨胀问题

    • 使用参数化工厂替代多子类
    class DynamicUIFactory {  constructor(config) {  this.config = config;  }  createButton() {  return new this.config.buttonClass();  }  
    }  
    
  3. 与构造函数对比

    工厂模式构造函数
    返回任意对象必须返回当前类实例
    可缓存/复用实例每次调用创建新实例
    支持更复杂的创建逻辑适合简单初始化场景

现代框架中的演进
  1. React组件工厂

    function createComponent(type, props) {  const components = {  Button: <button {...props} />,  Input: <input {...props} />  };  return components[type];  
    }  // 使用  
    <div>{createComponent('Button', { onClick })}</div>  
    
  2. Vue插件工厂

    const pluginFactory = (options) => ({  install(app) {  app.component(options.name, options.component);  }  
    });  Vue.use(pluginFactory({  name: 'CustomButton',  component: ButtonComponent  
    }));  
    
  3. Node.js模块缓存

    // Node.js的require机制本质是工厂模式  
    const express = require('express');  
    const app = express();  
    

模式延伸
  1. 工厂方法 vs 抽象工厂

    一个方法创建一种产品
    多个方法创建产品族
    工厂方法
    单一产品层级
    抽象工厂
    多个关联产品
  2. 与原型模式结合

    class PrototypeFactory {  constructor(prototypes) {  this.prototypes = prototypes;  }  create(type) {  return this.prototypes[type].clone();  }  
    }  
    
  3. 依赖注入实现

    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中最基础的组织代码模式,通过闭包和立即执行函数实现作用域隔离,核心目标是封装私有状态,暴露公共接口。以下是其关键要点:


核心特性
特性说明
命名空间避免全局变量污染
私有封装通过闭包保护内部状态
接口暴露选择性公开方法与属性
依赖管理显式声明模块依赖关系

应用场景
  1. 工具库开发

    • Lodash/Underscore等工具函数封装
    • 日期处理/数学计算专用模块
  2. 第三方SDK

    • 支付SDK(支付宝/微信支付接口封装)
    • 地图SDK(Google Maps API包装)
  3. 业务模块

    • 用户认证模块(登录/注册/权限)
    • 购物车管理模块
  4. 旧代码重构

    • 将全局函数重构为模块
    • 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);  

实现流程图解

在这里插入图片描述


注意事项
  1. 内存泄漏

    • 避免在闭包中保留DOM引用
    function createModule() {  const element = document.getElementById('app'); // 危险!  return {  update() { /*...*/ }  };  
    }  
    
  2. 单元测试困难

    • 通过依赖注入解决
    const TestableModule = (deps = { fetch: fetch }) => ({  getData() {  return deps.fetch('/data');  }  
    });  
    
  3. 模块通信

    通信方式优点缺点
    全局事件总线松耦合难以追踪数据流
    直接引用简单直接增加耦合度
    依赖注入可测试性好增加配置复杂度

现代框架中的演进
  1. ES6 Modules

    // math.js  
    export function add(a, b) {  return a + b;  
    }  // app.js  
    import { add } from './math.js';  
    
  2. Webpack模块联邦

    // app1 暴露模块  
    new ModuleFederationPlugin({  name: "app1",  exposes: {  "./Button": "./src/Button.js"  }  
    });  // app2 使用远程模块  
    import("app1/Button").then(Button => {  ReactDOM.render(<Button />, root);  
    });  
    
  3. Node.js CommonJS

    // 模块导出  
    module.exports = {  method: () => { /*...*/ }  
    };  // 模块导入  
    const lib = require('./lib');  
    

模式延伸
  1. 增强模块模式

    const EnhancedModule = (original => ({  ...original,  newMethod() {  // 扩展功能  }  
    }))(UserModule);  
    
  2. 沙箱模式

    function Sandbox(...deps) {  const modules = {};  return {  register(name, impl) {  modules[name] = impl;  },  require(name) {  return modules[name];  }  };  
    }  
    
  3. 动态加载

    const DynamicModule = {  load(name) {  return import(`./modules/${name}.js`);  }  
    };  
    

模块化演进史

IIFE
CommonJS
AMD
UMD
ES6 Modules
Bundle Systems
Micro Frontends

最佳实践组合

// 现代模块化方案  
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种前端开发中常用的设计模式,其他设计模式将在下篇文章中继续分享哦,敬请期待~

相关文章:

  • TMI投稿指南(二):投稿文章注意事项
  • 维安WAYON推出32位MCU:WY32F1030系列
  • Ajax 提交表单与文件上传
  • 快乐数(双指针解法)
  • Spring框架allow-bean-definition-overriding详细解释
  • 永磁同步电机控制算法-转速环电流环SMC控制器
  • 微信jdk 前端vue获取流程1、
  • 基于【低代码+AI智能体】开发智能考试系统
  • 构建“云中”高并发:12306技术改造的系统性启示
  • leetcode11-盛水最多的容器
  • Druid监控sql导致的内存溢出
  • 蓝桥杯 3. 压缩字符串
  • oracle 数据库查询指定用户下每个表占用空间的大小,倒序显示
  • MATLAB Coder代码生成(工业部署)——MATLAB技巧
  • 2025系统架构师---基于规则的系统架构风格‌
  • 龙虎榜——20250428
  • 1.9多元函数积分学
  • 报表工具:企业数据决策的“智能翻译官“
  • matlab中的Simscape的调用-入门
  • [特殊字符] SpringCloud项目中使用OpenFeign进行微服务远程调用详解(含连接池与日志配置)
  • 言短意长|政府食堂、停车场开放的示范效应
  • 2025上海车展 | 当智驾不再让人兴奋,汽车智能化暗战升级
  • 商务部:4月份以来的出口总体延续平稳增长态势
  • 大家聊中国式现代化|陶希东:打造高水平安全韧性城市,给群众看得见的安全感
  • 新加坡选情渐热:播客、短视频各显神通,总理反对身份政治
  • 三大猪企去年净利润同比均较大幅度增长,资产负债率齐降