代码写成一锅粥?这5种设计模式让你的项目“起死回生”

你是不是见过这样的代码:一个文件几千行,一个函数做了十件事,改一个地方崩三个地方。今天我们不背理论,直接用5种前端最常用的设计模式,把你从"面条代码"里捞出来。学完你会发现:原来代码可以像乐高一样,哪里坏了换哪里。

前言

设计模式不是"高大上"的面试题,而是前辈们踩过无数坑后总结的"套路"。就像下棋有定式,写代码也有常见问题的标准解法。今天我们从实际场景出发,不讲23种全部,只挑前端最常用的5种:单例、观察者、工厂、策略、装饰器。看完你就能立刻用在项目里。

一、单例模式:全局只有一个的"独生子"

场景:全局弹窗、登录框、Store、线程池。你希望整个应用只有一个实例,反复创建会浪费资源或导致状态冲突。

不用的痛 :每次调用都new Modal(),结果页面上出现十几个重叠的弹窗。

实现

js 复制代码
class Singleton {
  constructor() {
    if (!Singleton.instance) {
      this.data = [];
      Singleton.instance = this;
    }
    return Singleton.instance;
  }
  add(item) {
    this.data.push(item);
  }
}
const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true

前端更常见的写法:用闭包或模块(ES6模块本身就是单例)。

js 复制代码
// modal.js
let instance;
export function getModal() {
  if (!instance) {
    instance = new Modal();
  }
  return instance;
}

现代替代 :直接导出对象字面量(export default { show() {} }),ES6模块天然单例。

二、观察者模式:让不相干的组件"悄悄对话"

场景:购物车更新后,导航栏的数字要变、价格要重算、埋点要上报。你不想让购物车直接调用导航栏的方法(耦合太紧)。

不用的痛 :购物车里写header.updateCartCount()sidebar.recalculate()analytics.track()...每加一个模块,购物车代码就要改一次。

实现

js 复制代码
class EventBus {
  constructor() {
    this.events = {};
  }
  on(event, callback) {
    if (!this.events[event]) this.events[event] = [];
    this.events[event].push(callback);
  }
  emit(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(cb => cb(data));
    }
  }
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
}
const bus = new EventBus();
// 购物车模块
bus.emit('cartUpdated', { count: 3 });
// 导航栏模块
bus.on('cartUpdated', (data) => updateCount(data.count));
// 埋点模块
bus.on('cartUpdated', (data) => track('cart', data));

观察者模式是前端最常用的模式之一。Vue的响应式原理、React的事件系统、Node.js的EventEmitter都是它的变体。

三、工厂模式:不用自己 new,让"工厂"替你造

场景 :根据不同参数创建不同类型的对象,但创建逻辑复杂(比如需要条件判断、依赖注入)。你不想在业务代码里到处写newif-else

不用的痛 :每个用到按钮的地方都要写一堆if (type === 'primary') return new PrimaryButton()...重复代码爆炸。

实现

js 复制代码
class ButtonFactory {
  createButton(type) {
    switch(type) {
      case 'primary':
        return new PrimaryButton();
      case 'danger':
        return new DangerButton();
      default:
        return new DefaultButton();
    }
  }
}
const factory = new ButtonFactory();
const btn = factory.createButton('primary');

工厂模式把创建对象的逻辑集中管理,业务代码只依赖工厂接口。

更简单的函数工厂

js 复制代码
function createUser(role) {
  const base = { createdAt: Date.now() };
  if (role === 'admin') {
    return { ...base, permissions: ['read', 'write', 'delete'] };
  }
  return { ...base, permissions: ['read'] };
}

四、策略模式:消灭"if-else 毒瘤"

场景 :表单校验:用户名规则、密码规则、邮箱规则各不相同。或者根据用户等级计算折扣:普通会员9折,黄金会员8折,钻石会员7折。你不想写一长串if-else

不用的痛 :一个函数里十几个if-else,加一个新策略要改原有代码,还容易引入bug。

实现

js 复制代码
// 策略对象
const discountStrategies = {
  normal: (price) => price * 0.9,
  gold: (price) => price * 0.8,
  diamond: (price) => price * 0.7,
};
function getDiscount(level, price) {
  return discountStrategies[level]?.(price) ?? price;
}
// 使用时
const finalPrice = getDiscount('gold', 100); // 80

校验示例:

js 复制代码
const validators = {
  required: (val) => val.trim() !== '',
  minLength: (val, len) => val.length >= len,
  email: (val) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)
};
function validate(value, rules) {
  for (let rule of rules) {
    const [name, param] = rule.split(':');
    if (!validators[name]?.(value, param)) return false;
  }
  return true;
}

策略模式把算法(策略)提取成独立对象,可以动态替换、复用。

五、装饰器模式:给代码"贴金"而不改源码

场景:给现有函数添加日志、性能监控、权限校验、缓存功能,但不修改函数本身。

不用的痛 :在每个函数内部手动加console.time,加完又删,污染业务逻辑。

实现(JavaScript高阶函数版本,TS装饰器已在之前文章讲过):

js 复制代码
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(2, 3); // 输出日志,返回5

更通用的装饰器组合:

js 复制代码
function withTimer(fn) {
  return function(...args) {
    const start = performance.now();
    const result = fn.apply(this, args);
    const end = performance.now();
    console.log(`${fn.name} 耗时 ${end - start}ms`);
    return result;
  };
}
// 组合多个装饰器
const enhanced = withLog(withTimer(add));

装饰器模式让你能"叠加"功能,保持单一职责。

六、实际项目中的组合运用

比如一个用户登录模块:

  • 单例模式:全局唯一的UserStore
  • 工厂模式:根据角色创建不同的用户实例(createUser('admin'))。
  • 观察者模式:登录成功后,触发userLoggedIn事件,购物车、头像组件、权限菜单分别响应。
  • 策略模式:不同等级用户的权限校验策略。
  • 装饰器模式:给API请求函数加上缓存、重试、日志。

七、总结:设计模式是"招式",不是"教条"

  • 单例:全局唯一,省资源。
  • 观察者:解耦事件发布和订阅。
  • 工厂:集中创建对象。
  • 策略:消灭if-else,算法可互换。
  • 装饰器:动态增强功能。

不要为了用模式而用模式。当你的代码出现重复、难维护、改一处动全身时,想想哪种模式能帮你解耦。写代码就像搭积木,模式是那些标准接口的积木块,让你搭得又快又稳。


如果你觉得今天的"招式"够实用,点个赞让更多人看到。明天我们将聊聊前端架构设计------从技术选型到目录结构,如何搭建一个能支撑三年迭代的项目骨架。我们明天见!

相关推荐
ywlovecjy2 小时前
【Nginx 】Nginx 部署前端 vue 项目
前端·vue.js·nginx
skywalk81632 小时前
g4f JavaScript调用报错问题解决
开发语言·javascript·ecmascript
Alice-YUE2 小时前
AI对话为什么需要RAG
前端·语言模型·rag
C澒2 小时前
IntelliPro 企业级产研协作平台:低代码实时预览与可视化编辑技术调研
前端·低代码·ai编程
霍理迪2 小时前
TS类型断言和类型守卫
前端
木斯佳2 小时前
前端八股文面经大全:京东前端实习一面(2026-04-16)·面经深度解析
前端
chenxu98b2 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
Bigger2 小时前
第十章:我是如何剖析 CLI 里的终极 Agent 能力的(电脑控制与浏览器接管)
前端·claude·源码阅读
coder_liluyao2 小时前
JS动画函数的封装(很实用)
javascript