欢迎光临

JavaScript设计模式实战指南:从基础到工程化应用的10种核心模式

JavaScript代码编辑器

为什么设计模式在JavaScript中依然重要

设计模式(Design Patterns)是软件工程中经过反复验证的、针对特定问题的通用解决方案。虽然现代JavaScript框架(React、Vue、Angular)已经内置了许多设计模式的实现,但深入理解这些模式仍然对写出高质量、可维护的代码至关重要。

很多开发者认为设计模式是Java/C++等传统OOP语言的专利,JavaScript的灵活性让设计模式变得不再必要。这种观点其实是一种误解。JavaScript的原型继承、函数式特性、闭包等语言特性,恰恰让设计模式在JS中有了更优雅、更简洁的实现方式。

本文将深入探讨10种在JavaScript开发中最常用的设计模式,从基础概念到工程实践,每种模式都配有完整的代码示例和真实应用场景分析。

创建型模式:灵活控制对象创建

1. 单例模式(Singleton)

单例模式确保一个类只有一个实例,并提供一个全局访问点。在JavaScript中,单例模式常用于管理全局状态、配置对象、日志记录器、数据库连接池等场景。

ES6的模块系统天然支持单例模式,因为模块只会被导入一次:


1
2
3
4
5
6
7
8
9
10
11
// config.js - ES6模块天然单例
const CONFIG = {
  apiBaseUrl: 'https://api.example.com',
  timeout: 5000,
  retryCount: 3
};

export default CONFIG;

// 使用:无论多少次 import,CONFIG 都是同一个对象
import config from './config.js';

如果需要更传统的类式单例,可以使用闭包实现:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    this.createdAt = new Date();
    Singleton.instance = this;
  }

  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

const a = new Singleton();
const b = Singleton.getInstance();
console.log(a === b); // true

实际应用场景:Vuex/Pinia的Store、Redux的Store、EventBus事件总线、全局Loading状态管理。

2. 工厂模式(Factory)

工厂模式将对象的创建逻辑封装在工厂函数中,客户端不需要知道具体的类名和构造细节。这在复杂对象创建场景下非常有用。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 用户角色工厂
class AdminUser {
  constructor(name) { this.role = 'admin'; this.name = name; }
  getPermissions() { return ['read', 'write', 'delete', 'manage_users']; }
}

class EditorUser {
  constructor(name) { this.role = 'editor'; this.name = name; }
  getPermissions() { return ['read', 'write']; }
}

class ViewerUser {
  constructor(name) { this.role = 'viewer'; this.name = name; }
  getPermissions() { return ['read']; }
}

// 工厂函数
function createUser(role, name) {
  const roleMap = { admin: AdminUser, editor: EditorUser, viewer: ViewerUser };
  const UserClass = roleMap[role];
  if (!UserClass) throw new Error(`未知角色: ${role}`);
  return new UserClass(name);
}

// 使用
const user = createUser('admin', '张三');
console.log(user.getPermissions()); // ['read', 'write', 'delete', 'manage_users']

实际应用场景:React的createElement、不同环境下的Storage适配器、多态组件渲染、API响应数据格式化。

JavaScript代码结构

结构型模式:优化对象组合与接口

3. 适配器模式(Adapter)

适配器模式让不兼容的接口能够协同工作。在前端开发中,经常需要适配不同第三方库的API差异。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 第三方支付SDK - 接口A
class WechatPaySDK {
  wxPay(amount, orderId) {
    return `微信支付: ${amount}元, 订单: ${orderId}`;
  }
}

// 第三方支付SDK - 接口B
class AlipaySDK {
  aliPay(params) {
    return `支付宝支付: ${params.total}元, 订单: ${params.tradeNo}`;
  }
}

// 统一适配器
class PaymentAdapter {
  constructor(paymentSystem) {
    this.payment = paymentSystem;
  }

  pay(amount, orderId) {
    if (this.payment instanceof WechatPaySDK) {
      return this.payment.wxPay(amount, orderId);
    } else if (this.payment instanceof AlipaySDK) {
      return this.payment.aliPay({ total: amount, tradeNo: orderId });
    }
    throw new Error('不支持的支付方式');
  }
}

// 统一调用
const payment = new PaymentAdapter(new WechatPaySDK());
console.log(payment.pay(99.9, 'ORDER20260717'));

实际应用场景:浏览器兼容性API适配、数据格式转换器、第三方SDK统一封装、不同后端API接口对齐。

4. 装饰器模式(Decorator)

装饰器模式动态地为对象添加新行为,而不改变其原有结构。JavaScript中的高阶函数和原型链非常适合实现装饰器。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 基础函数
function fetchData(url) {
  console.log(`请求: ${url}`);
  return fetch(url).then(res => res.json());
}

// 装饰器:添加缓存
function withCache(fn, ttl = 60000) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    const cached = cache.get(key);
    if (cached && Date.now() - cached.timestamp < ttl) {
      console.log('命中缓存');
      return Promise.resolve(cached.data);
    }
    return fn.apply(this, args).then(data => {
      cache.set(key, { data, timestamp: Date.now() });
      return data;
    });
  };
}

// 装饰器:添加重试
function withRetry(fn, maxRetries = 3) {
  return async function(...args) {
    let lastError;
    for (let i = 0; i < maxRetries; i++) {
      try {
        return await fn.apply(this, args);
      } catch (err) {
        lastError = err;
        console.log(`重试第 ${i + 1} 次`);
        await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
      }
    }
    throw lastError;
  };
}

// 组合使用
const cachedFetch = withCache(fetchData);
const robustFetch = withRetry(withCache(fetchData));

实际应用场景:Express/Koa中间件链、React高阶组件(HOC)、TypeScript装饰器、日志/监控/鉴权横切关注点。

5. 代理模式(Proxy)

代理模式为另一个对象提供一个替身或占位符,以控制对它的访问。ES6的Proxy对象让JavaScript的代理模式实现变得异常强大。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 验证代理
const userValidator = {
  set(target, key, value) {
    const rules = {
      name: v => typeof v === 'string' && v.length >= 2,
      age: v => typeof v === 'number' && v >= 0 && v <= 150,
      email: v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)
    };
    if (rules[key] && !rules[key](value)) {
      throw new Error(`属性 ${key} 的值 ${value} 不合法`);
    }
    target[key] = value;
    return true;
  }
};

const user = new Proxy({}, userValidator);
user.name = '张三';  // OK
user.age = 25;       // OK
try {
  user.age = 200;    // 抛出错误
} catch (e) {
  console.error(e.message);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 懒加载代理 - 图片延迟加载
class LazyImage {
  constructor(src) {
    this.src = src;
    this.element = document.createElement('div');
    this.element.className = 'lazy-image-placeholder';
    this.loaded = false;
  }

  render() {
    if (this.loaded) return this.element;
    const img = new Image();
    img.onload = () => {
      this.element.style.backgroundImage = `url(${this.src})`;
      this.loaded = true;
    };
    img.src = this.src;
    return this.element;
  }
}

实际应用场景:Vue 3响应式系统、数据验证、图片懒加载、API请求防抖节流、权限控制。

行为型模式:优化对象间通信

6. 观察者模式(Observer)

观察者模式定义了一种一对多的依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会自动收到通知。这是JavaScript中应用最广泛的设计模式之一。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class EventBus {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
    return () => this.off(event, callback);
  }

  off(event, callback) {
    if (!this.events[event]) return;
    this.events[event] = this.events[event].filter(cb => cb !== callback);
  }

  emit(event, ...args) {
    if (!this.events[event]) return;
    this.events[event].forEach(cb => cb(...args));
  }

  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

// 使用示例
const bus = new EventBus();

const unsub = bus.on('user:login', (user) => {
  console.log(`${user.name} 登录了`);
});

bus.emit('user:login', { name: 'admin' });
unsub();

实际应用场景:Vue的响应式系统、React的事件系统、DOM事件监听、WebSocket消息分发、跨组件通信。

7. 策略模式(Strategy)

策略模式定义一系列算法,把它们封装起来,并使它们可以互相替换。策略模式让算法可以独立于使用它的客户端变化。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 价格计算策略
const pricingStrategies = {
  regular: (price) => price,
  vip: (price) => price * 0.8,
  enterprise: (price) => {
    let result = price * 0.7;
    if (result >= 1000) result -= 100;
    return result;
  },
  promotion: (price) => {
    let result = price;
    if (price >= 200) result -= 50;
    return result * 0.9;
  }
};

class PriceCalculator {
  constructor(strategy = 'regular') {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
  }

  calculate(price) {
    const fn = pricingStrategies[this.strategy];
    if (!fn) throw new Error(`未知策略: ${this.strategy}`);
    return fn(price);
  }
}

const calculator = new PriceCalculator();
console.log(calculator.calculate(100));
calculator.setStrategy('vip');
console.log(calculator.calculate(100));
calculator.setStrategy('promotion');
console.log(calculator.calculate(250));

实际应用场景:表单验证规则、排序算法切换、数据格式化、支付方式选择、权限校验逻辑。

8. 命令模式(Command)

命令模式将请求封装为对象,从而支持参数化、排队、日志化、撤销操作等。在需要实现Undo/Redo功能的场景中特别有用。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class CommandHistory {
  constructor() {
    this.history = [];
    this.index = -1;
  }

  execute(command) {
    command.execute();
    this.history = this.history.slice(0, this.index + 1);
    this.history.push(command);
    this.index++;
  }

  undo() {
    if (this.index < 0) return;
    this.history[this.index].undo();
    this.index--;
  }

  redo() {
    if (this.index + 1 >= this.history.length) return;
    this.index++;
    this.history[this.index].execute();
  }
}

class InsertCommand {
  constructor(textArea, text, position) {
    this.textArea = textArea;
    this.text = text;
    this.position = position;
  }

  execute() {
    this.textArea.insert(this.text, this.position);
  }

  undo() {
    this.textArea.delete(this.position, this.position + this.text.length);
  }
}

class DeleteCommand {
  constructor(textArea, start, end) {
    this.textArea = textArea;
    this.start = start;
    this.end = end;
    this.deletedText = '';
  }

  execute() {
    this.deletedText = this.textArea.getText(this.start, this.end);
    this.textArea.delete(this.start, this.end);
  }

  undo() {
    this.textArea.insert(this.deletedText, this.start);
  }
}

实际应用场景:文本编辑器Undo/Redo、操作日志记录、事务处理、宏录制、批量操作队列。

JavaScript编程概念

现代JavaScript中的模式实践

9. 组合模式与React组件

组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。React的组件树天然就是组合模式的实现。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// React 组合模式示例
function Card({ children, title, footer }) {
  return (
    &lt;div className=&quot;card&quot;&gt;
      {title &amp;&amp; &lt;div className=&quot;card-header&quot;&gt;{title}&lt;/div&gt;}
      &lt;div className=&quot;card-body&quot;&gt;{children}&lt;/div&gt;
      {footer &amp;&amp; &lt;div className=&quot;card-footer&quot;&gt;{footer}&lt;/div&gt;}
    &lt;/div&gt;
  );
}

function App() {
  return (
    &lt;Card
      title=&quot;用户信息&quot;
      footer={&lt;button&gt;保存&lt;/button&gt;}
    &gt;
      &lt;UserForm /&gt;
    &lt;/Card&gt;
  );
}

10. 发布-订阅模式与跨组件通信

发布-订阅模式是观察者模式的升级版,通过消息中心解耦发布者和订阅者。Vue的EventBus、React的Context API都是这一模式的变体。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 类型安全的发布订阅
type EventMap = {
  'user:login': { userId: string; timestamp: number };
  'user:logout': { userId: string };
  'cart:update': { items: number; total: number };
  'notification:new': { message: string; type: 'info' | 'error' };
};

class TypedEventBus&lt;T extends Record&lt;string, any&gt;&gt; {
  private listeners = new Map&lt;keyof T, Set&lt;Function&gt;&gt;();

  on&lt;K extends keyof T&gt;(event: K, callback: (data: T[K]) =&gt; void): () =&gt; void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(callback);
    return () =&gt; this.listeners.get(event)?.delete(callback);
  }

  emit&lt;K extends keyof T&gt;(event: K, data: T[K]): void {
    this.listeners.get(event)?.forEach(cb =&gt; cb(data));
  }
}

const bus = new TypedEventBus&lt;EventMap&gt;();
bus.on('user:login', (data) =&gt; {
  console.log(`用户 ${data.userId} 在 ${data.timestamp} 登录`);
});
bus.emit('user:login', { userId: 'u123', timestamp: Date.now() });

设计模式的选择原则

在实际开发中,选择合适的设计模式比记住所有的模式更加重要。以下是一些实用原则:

场景 推荐模式 反模式
需要全局唯一实例 单例模式 全局变量裸用
对象创建逻辑复杂 工厂模式 大量new操作散落各处
需要动态添加功能 装饰器模式 大量继承子类
控制对象访问 代理模式 在业务代码中嵌入控制逻辑
一对多通知 观察者模式 轮询或耦合回调
算法可互换 策略模式 大量if-else/switch
需要撤销操作 命令模式 直接修改状态无法回退
不兼容接口对接 适配器模式 修改第三方库代码

总结:设计模式是思想的工具,不是教条

设计模式的核心价值在于提供了一套经过验证的沟通词汇和解决方案体系。当一个团队都说"这里用观察者模式好"时,他们不需要重新解释什么是解耦、什么是一对多关系——这就是设计模式最大的价值。

在JavaScript开发中,我们应该记住以下几点:

  • 不要为了用模式而用模式——模式是解决问题的手段,不是目的。过度设计比没有设计更糟糕。
  • JavaScript有自己的实现方式——闭包、高阶函数、Proxy、Symbol等语言特性让很多模式实现更加简洁,无需照搬Java的写法。
  • 框架已经帮你实现了大部分模式——React的组合、Vue的响应式、Express的中间件都内置了设计模式思想。理解这些模式有助于你更好地使用框架。
  • 模式是活的——不要被23种经典GoF模式所限。现代JavaScript实践中,模块模式、Revealing Module、Mixin等变体同样重要。

最后,真正的高手不是记住所有模式的人,而是能在合适的场景中自然运用合适模式的人。希望本文的10种模式能成为你JavaScript工具箱中的锐利武器。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » JavaScript设计模式实战指南:从基础到工程化应用的10种核心模式
分享到: 更多 (0)