010-JavaScript设计模式+手撕

JavaScript 设计模式常见类型

1. 单例模式(Singleton):确保一个类只有一个实例,并提供全局访问点。

2. 工厂模式(Factory):定义一个创建对象的接口,由子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

3. 抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

4. 建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

5. 原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

6. 适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

7. 装饰者模式(Decorator):动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

8. 代理模式(Proxy):为其他对象提供一种代理以控制对这个对象的访问。

9. 观察者模式(Observer):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

10. 发布订阅模式(Pub-Sub):与观察者模式类似,但有一个中间的事件通道,发布者和订阅者不直接通信,而是通过这个通道来传递消息。

11. 策略模式(Strategy):定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。

12. 命令模式(Command):将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

13. 状态模式(State):允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

14. 模板方法模式(Template Method):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

15. 迭代器模式(Iterator):提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

16. 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。


1. 创建型模式

  • **单例模式 (Singleton)**
    确保一个类只有一个实例(如全局状态管理)。
  • **工厂模式 (Factory)**
    封装对象的创建过程(如 createElement())。
  • **建造者模式 (Builder)**
    分步骤构建复杂对象(如配置对象生成)。

2. 结构型模式

  • **适配器模式 (Adapter)**
    兼容不同接口(如数据格式转换)。
  • **装饰器模式 (Decorator)**
    动态扩展对象功能(ES7/TypeScript 装饰器)。
  • **代理模式 (Proxy)**
    控制对象访问(如 ES6 Proxy 对象)。

3. 行为型模式

  • **观察者模式 (Observer)**
    直接通知依赖对象(如 MutationObserver)。
  • **发布订阅模式 (Pub/Sub)**
    通过事件中心通信(如 EventEmitter)。
  • **策略模式 (Strategy)**
    封装可互换的算法(如验证规则集)。
  • **迭代器模式 (Iterator)**
    提供遍历集合的方法(如 ES6 Iterator)。

观察者 (Observer) vs 发布订阅 (Pub/Sub) 的区别

特性 观察者模式 发布订阅模式
通信方式 直接通信 通过事件中心间接通信
耦合关系 观察者与目标相互知晓(较高耦合) 发布/订阅者彼此独立(完全解耦)
灵活性 依赖关系固定 动态订阅/取消,支持多对多关系
典型场景 对象状态变化直接通知 跨模块通信、事件驱动架构
实现复杂度 较简单 需维护事件中心(略复杂)

核心区别图解

观察者模式:   Subject →→ 直接通知 →→ Observer
发布订阅模式: Publisher → 事件中心 → Subscriber

手写发布订阅模式实现

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
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) {
const callbacks = this.events[eventName];
if (callbacks) {
callbacks.forEach(cb => cb(...args)); // 触发所有回调
}
}

// 取消订阅
off(eventName, callback) {
const callbacks = this.events[eventName];
if (callbacks) {
this.events[eventName] = callbacks.filter(cb => cb !== callback);
}
}

// 单次订阅
once(eventName, callback) {
const onceWrapper = (...args) => {
callback(...args);
this.off(eventName, onceWrapper); // 执行后立即取消
};
this.on(eventName, onceWrapper);
}
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const bus = new EventEmitter();

// 订阅事件
bus.on("message", (data) => {
console.log(`收到消息: ${data}`);
});

// 单次订阅
bus.once("alert", (text) => {
console.log(`一次性提示: ${text}`);
});

// 发布事件
bus.emit("message", "Hello World"); // 输出: 收到消息: Hello World
bus.emit("alert", "仅出现一次"); // 输出: 一次性提示: 仅出现一次
bus.emit("alert", "再次触发"); // 无输出(已取消)

// 取消订阅
const handler = (data) => console.log(`日志: ${data}`);
bus.on("log", handler);
bus.off("log", handler);
bus.emit("log", "测试"); // 无输出

关键特性

  • 解耦:发布/订阅者无需知道对方存在
  • 多对多:一个事件可被多次订阅,一次发布触发所有回调
  • 动态管理:支持运行时订阅/取消

实际应用场景:跨组件通信、插件系统、异步任务调度等。

011-JavaScript异步编程详解

JavaScript 异步编程的理解

核心本质

JavaScript 是单线程语言,但浏览器/Node.js 提供了异步非阻塞的运行环境。异步编程的核心目的是解决单线程环境下 I/O 操作等阻塞问题,实现高效并发处理。

关键机制

  1. 事件循环 (Event Loop)

    • 调用栈 (Call Stack):同步代码执行栈
    • 任务队列 (Task Queue):
      • 宏任务队列 (Macrotask):setTimeoutsetInterval、I/O 操作
      • 微任务队列 (Microtask):Promise.then()MutationObserver
    • 执行顺序:同步代码 → 清空微任务队列 → 执行一个宏任务 → 重复
  2. 执行模型

    1
    2
    3
    4
    5
    6
    graph LR
    A[同步代码] --> B{栈空?}
    B -->|是| C[执行微任务]
    C --> D[取宏任务]
    D --> E[执行宏任务]
    E --> C

演进历程

阶段 方案 特点 问题
Callback 回调函数 基础异步支持 回调地狱,错误处理困难
Promise ES6 原生 链式调用,解决回调地狱 仍需 then/catch
Generator function* + yield 暂停/恢复执行 需要执行器(co库)
Async/Await ES2017 同步写法处理异步,终极方案 语法糖(基于 Promise)

代码示例演进

  1. Callback Hell
1
2
3
4
5
6
getData('/api', (err, data1) => {
if (err) handleError(err);
getData(`/api/${data1.id}`, (err, data2) => {
// 更多嵌套...
});
});
  1. Promise 链
1
2
3
4
5
fetch('/api')
.then(response => response.json())
.then(data1 => fetch(`/api/${data1.id}`))
.then(response => response.json())
.catch(err => console.error(err));
  1. Async/Await (推荐)
1
2
3
4
5
6
7
8
9
10
11
async function loadData() {
try {
const res1 = await fetch('/api');
const data1 = await res1.json();

const res2 = await fetch(`/api/${data1.id}`);
return await res2.json();
} catch (err) {
console.error('加载失败', err);
}
}

实际应用场景

  1. 网络请求fetch/axios 数据获取
  2. 定时操作setTimeout/setInterval
  3. 文件操作:Node.js 中 fs.readFile
  4. 用户交互:事件监听 addEventListener
  5. Web Workers:CPU 密集型任务分流

最佳实践

  1. 优先使用 async/await 代替回调
  2. Promise.all() 处理并行异步
1
2
3
4
const [user, posts] = await Promise.all([
fetch('/user'),
fetch('/posts')
]);
  1. 重要操作添加超时控制
1
2
3
4
5
6
7
8
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), ms)
)
]);
}

常见误区

  1. 认为 async 函数是同步执行
    (实际只是同步写法,本质仍是异步)

  2. 在循环中错误使用 await

    1
    2
    3
    4
    5
    6
    7
    // 错误:顺序执行
    for (const url of urls) {
    await fetch(url);
    }

    // 正确:并行执行
    await Promise.all(urls.map(url => fetch(url)));
  3. 忽略错误处理
    (未使用 try/catch 或 catch 处理 Promise)

总结

JavaScript 异步编程通过事件循环机制实现单线程下的高并发处理。从回调函数到 Promise 再到 Async/Await 的演进,使异步代码可读性和可维护性大幅提升。理解事件循环、任务队列等底层机制,并合理运用现代异步语法,是编写高效 JavaScript 应用的关键。