目录

3.观察者模式(上)

1. 观察者模式的核心定位

生活案例:快速理解核心角色

2. 代码实现:从基础抽象到业务落地

核心要点回顾

案例练习:电商订单状态通知系统

需求场景

TS实现的核心优越性总结

4. 观察者模式(下)

1.典型场景

1. Vue响应式系统:观察者模式的经典落地

2. 高频面试题:实现Event Bus/Event Emitter

3. 关键区别:观察者模式 vs 发布-订阅模式

2. 案例练习:用户关注博主的消息通知系统

场景描述

TS实现的优越性总结

5.迭代器模式

1. 迭代器模式的核心定义

2.ES6前的迭代器困境与解决方案

1 核心困境:类数组的遍历难题

3. ES6对迭代器的原生支持

(1)核心机制:Symbol.iterator属性

(2)next()方法的逻辑

(3)for...of的底层原理

4. 自定义迭代器生成函数实现

迭代器模式的核心价值

5. 案例练习:自定义购物车商品迭代器

TS实现的核心优越性

3.观察者模式(上)

1. 观察者模式的核心定位

  • 重要性:JS中使用频率最高、面试频率最高的设计模式,覆盖前端核心场景(如DOM事件监听、Vue/React状态管理、异步回调),是面试官重点考查的设计模式之一。
  • 官方定义:定义一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象;当目标对象的状态发生变化时,会自动通知所有观察者,使它们执行对应更新逻辑。
  • 别名与差异:常被称为“发布-订阅模式”(两者核心逻辑一致,细微差异在于“角色分工粒度”——观察者模式更侧重“目标与观察者的直接依赖”,发布-订阅模式多了“中间调度层”,但日常开发中可通用)。

生活案例:快速理解核心角色

文章以“产品经理拉群发需求”为案例,清晰拆解观察者模式的3个核心角色和流程:

  1. 角色对号入座
    • 发布者(目标对象):韩梅梅(产品经理),负责“管理需求状态”和“通知订阅者”(拉群=添加订阅者,发需求=通知);
    • 观察者(订阅者):前端李雷、后端王五、测试赵六,负责“监听需求变化”(看群消息)和“执行响应逻辑”(接需求后开发/测试);
    • 目标状态:需求文档(从“无”到“有”,从“初稿”到“终稿”)。
  1. 核心流程:发布者维护订阅者列表 → 目标状态变化 → 发布者主动通知所有订阅者 → 订阅者接收通知并执行逻辑。

2. 代码实现:从基础抽象到业务落地

观察者模式的核心是“拆分发布者与订阅者,通过‘委托通知’解耦”,文章分两步实现:先封装通用能力,再适配具体业务。

先定义“发布者”和“观察者”的通用接口,抽离重复逻辑(如订阅者的增删、通知触发),供后续业务类扩展:

// 基础发布者类:管理订阅者、提供“增删订阅者”和“通知”能力
class Publisher {
  constructor() {
    this.observers = []; // 存储订阅者列表
  }

  // 1. 添加订阅者(避免重复添加)
  add(observer) {
    if (!this.observers.includes(observer)) {
      this.observers.push(observer);
    }
  }

  // 2. 移除订阅者
  remove(observer) {
    this.observers = this.observers.filter(item => item !== observer);
  }

  // 3. 通知所有订阅者(传递自身,让订阅者获取最新状态)
  notify() {
    this.observers.forEach(observer => observer.update(this));
  }
}

// 基础观察者类:定义“接收通知”的统一接口(update方法)
class Observer {
  // 子类需重写此方法,实现具体响应逻辑
  update(publisher) {
    console.log('订阅者接收通知,待实现具体逻辑', publisher);
  }
}

基于基础类扩展,实现“需求文档发布-接收”的真实业务逻辑:

// 具体发布者:需求文档管理器(维护需求状态)
class PrdPublisher extends Publisher {
  constructor() {
    super();
    this.prdState = null; // 需求文档状态(初始为空)
  }

  // 获取当前需求状态(给订阅者调用)
  getState() {
    return this.prdState;
  }

  // 更新需求状态(触发通知的核心方法)
  setState(newPrd) {
    this.prdState = newPrd; // 更新状态
    this.notify(); // 状态变了,立刻通知所有订阅者
  }
}

// 具体观察者:开发者(接收需求后开始工作)
class DeveloperObserver extends Observer {
  constructor(name) {
    super();
    this.name = name; // 开发者姓名(个性化标识)
    this.myPrd = null; // 本地存储接收的需求
  }

  // 重写update:接收需求并执行工作
  update(publisher) {
    this.myPrd = publisher.getState(); // 从发布者获取最新需求
    this.work(); // 执行具体工作
  }

  // 开发者的核心工作逻辑
  work() {
    console.log(`${this.name}:接收到需求《${this.myPrd.title}》,内容:${this.myPrd.content},开始996开发!`);
  }
}

// 测试:模拟业务流程
const hanMeiMei = new PrdPublisher(); // 发布者:韩梅梅(产品)
const liLei = new DeveloperObserver('李雷(前端)'); // 订阅者1:前端
const wangWu = new DeveloperObserver('王五(后端)'); // 订阅者2:后端

// 1. 拉群(添加订阅者)
hanMeiMei.add(liLei);
hanMeiMei.add(wangWu);

// 2. 发布需求(更新状态,触发通知)
hanMeiMei.setState({
  title: '员工管理系统V2.0',
  content: '新增考勤打卡、请假审批模块'
});

// 输出结果:
// 李雷(前端):接收到需求《员工管理系统V2.0》,内容:新增考勤打卡、请假审批模块,开始996开发!
// 王五(后端):接收到需求《员工管理系统V2.0》,内容:新增考勤打卡、请假审批模块,开始996开发!

核心要点回顾

  1. 角色不可缺:必须包含“发布者”(管状态+通知)和“观察者”(接通知+执行),两者通过“发布者.notify()”和“观察者.update()”建立通信;
  2. 解耦是核心:发布者不关心观察者“具体做什么”,只负责通知;观察者不关心发布者“状态怎么变”,只负责响应——新增观察者/发布者时无需修改原有逻辑(符合开放封闭原则);
  3. 状态传递:发布者通知时需传递自身(或最新状态),让观察者能主动获取最新信息(避免观察者依赖“全局状态”)。


案例练习:电商订单状态通知系统

需求场景

设计一个电商订单状态管理系统,核心逻辑:

  1. 发布者:订单管理器(维护订单状态:待付款→已付款→已发货→已签收);
  2. 观察者:3类通知服务(短信通知用户、物流系统同步、订单日志记录);
  3. 流程:订单状态变化(如用户付款)→ 订单管理器通知所有观察者 → 各观察者执行对应逻辑(发短信告知用户、同步物流单、记录日志)。
// 1. 基础发布者类(复用通用能力)
class Publisher {
  constructor() {
    this.observers = [];
  }

  add(observer) {
    if (!this.observers.includes(observer)) this.observers.push(observer);
  }

  remove(observer) {
    this.observers = this.observers.filter(item => item !== observer);
  }

  notify() {
    this.observers.forEach(observer => observer.update(this));
  }
}

// 2. 具体发布者:订单管理器
class OrderPublisher extends Publisher {
  constructor(orderId) {
    super();
    this.orderId = orderId; // 订单ID
    this.orderState = 'pending_payment'; // 初始状态:待付款
    // 定义合法状态列表(避免非法状态)
    this.validStates = ['pending_payment', 'paid', 'shipped', 'received'];
  }

  // 获取当前订单状态
  getOrderState() {
    return {
      orderId: this.orderId,
      state: this.orderState,
      stateText: this.getStateText(this.orderState)
    };
  }

  // 状态文本映射(提升可读性)
  getStateText(state) {
    const map = {
      pending_payment: '待付款',
      paid: '已付款',
      shipped: '已发货',
      received: '已签收'
    };
    return map[state] || '未知状态';
  }

  // 更新订单状态(触发通知)
  updateOrderState(newState) {
    if (!this.validStates.includes(newState)) {
      console.error(`订单${this.orderId}:非法状态${newState}`);
      return;
    }
    this.orderState = newState;
    console.log(`\n订单${this.orderId}状态变更为:${this.getStateText(newState)}`);
    this.notify(); // 状态变了,通知所有观察者
  }
}

// 3. 具体观察者:各类通知服务
// 观察者1:短信通知用户
class SmsNotifyObserver {
  update(publisher) {
    const { orderId, stateText } = publisher.getOrderState();
    console.log(`【短信通知】订单${orderId}状态更新为${stateText},请留意物流动态~`);
  }
}

// 观察者2:物流系统同步
class LogisticsSyncObserver {
  update(publisher) {
    const { orderId, state } = publisher.getOrderState();
    if (state === 'paid') {
      console.log(`【物流同步】订单${orderId}已付款,开始创建物流单...`);
    } else if (state === 'shipped') {
      console.log(`【物流同步】订单${orderId}已发货,分配快递单号:SF123456789`);
    }
  }
}

// 观察者3:订单日志记录
class OrderLogObserver {
  update(publisher) {
    const { orderId, state, stateText } = publisher.getOrderState();
    const time = new Date().toLocaleString();
    console.log(`【订单日志】${time} - 订单${orderId}:${state}(${stateText})`);
  }
}

// 4. 测试流程
// 初始化订单(订单ID:20240520001)
const order = new OrderPublisher('20240520001');
// 初始化3个观察者
const smsNotify = new SmsNotifyObserver();
const logisticsSync = new LogisticsSyncObserver();
const orderLog = new OrderLogObserver();

// 添加观察者(绑定通知服务)
order.add(smsNotify);
order.add(logisticsSync);
order.add(orderLog);

// 模拟订单状态变更
order.updateOrderState('paid'); // 待付款 → 已付款
order.updateOrderState('shipped'); // 已付款 → 已发货

// 输出结果:
// 订单20240520001状态变更为:已付款
// 【短信通知】订单20240520001状态更新为已付款,请留意物流动态~
// 【物流同步】订单20240520001已付款,开始创建物流单...
// 【订单日志】2024/5/20 10:00:00 - 订单20240520001:paid(已付款)
// 
// 订单20240520001状态变更为:已发货
// 【短信通知】订单20240520001状态更新为已发货,请留意物流动态~
// 【物流同步】订单20240520001已发货,分配快递单号:SF123456789
// 【订单日志】2024/5/20 10:30:00 - 订单20240520001:shipped(已发货)

TS通过接口约束结构枚举定义常量类型标注访问控制,解决JS“类型模糊、易出错”的问题,编译时即可发现错误,核心优越性体现在“类型安全”和“可维护性”:

// 1. 定义枚举:订单状态(避免魔法字符串,语义化+类型安全)
enum OrderStateEnum {
  PENDING_PAYMENT = 'pending_payment', // 待付款
  PAID = 'paid', // 已付款
  SHIPPED = 'shipped', // 已发货
  RECEIVED = 'received' // 已签收
}

// 2. 定义接口:约束发布者和观察者的结构(编译时强制校验)
interface IPublisher {
  observers: IObserver[];
  add(observer: IObserver): void;
  remove(observer: IObserver): void;
  notify(): void;
}

interface IObserver {
  // 泛型T:适配不同发布者的状态类型(灵活复用)
  update<T>(publisher: IPublisherWithState<T>): void;
}

// 带状态的发布者接口(继承IPublisher,增加状态获取方法)
interface IPublisherWithState<T> extends IPublisher {
  getState(): T;
}

// 3. 定义类型:订单状态结构(明确状态数据格式)
type OrderState = {
  orderId: string;
  state: OrderStateEnum;
  stateText: string;
  updateTime: string;
};

// 4. 基础发布者类:实现IPublisher接口(强制符合结构)
class Publisher implements IPublisher {
  observers: IObserver[] = []; // 类型标注:明确是IObserver数组

  add(observer: IObserver): void {
    if (!this.observers.includes(observer)) {
      this.observers.push(observer);
    }
  }

  remove(observer: IObserver): void {
    this.observers = this.observers.filter(item => item !== observer);
  }

  notify(): void {
    this.observers.forEach(observer => {
      // 断言为IPublisherWithState<OrderState>,明确状态类型
      observer.update<OrderState>(this as IPublisherWithState<OrderState>);
    });
  }
}

// 5. 具体发布者:订单管理器(实现IPublisherWithState<OrderState>)
class OrderPublisher extends Publisher implements IPublisherWithState<OrderState> {
  private orderId: string; // private:私有属性,避免外部篡改
  private state: OrderStateEnum = OrderStateEnum.PENDING_PAYMENT; // 初始状态

  constructor(orderId: string) {
    super();
    this.orderId = orderId;
  }

  // 实现getState:返回明确的OrderState类型
  getState(): OrderState {
    return {
      orderId: this.orderId,
      state: this.state,
      stateText: this.getStateText(this.state),
      updateTime: new Date().toLocaleString()
    };
  }

  // 状态文本映射(TS自动推断返回值类型)
  private getStateText(state: OrderStateEnum): string {
    const map: Record<OrderStateEnum, string> = {
      [OrderStateEnum.PENDING_PAYMENT]: '待付款',
      [OrderStateEnum.PAID]: '已付款',
      [OrderStateEnum.SHIPPED]: '已发货',
      [OrderStateEnum.RECEIVED]: '已签收'
    };
    return map[state];
  }

  // 更新订单状态:参数只能是OrderStateEnum(编译时防错)
  updateOrderState(newState: OrderStateEnum): void {
    this.state = newState;
    console.log(`\n订单${this.orderId}状态变更为:${this.getStateText(newState)}`);
    this.notify();
  }
}

// 6. 具体观察者:通用通知服务(通过类型约束确保逻辑安全)
class SmsNotifyObserver implements IObserver {
  update<OrderState>(publisher: IPublisherWithState<OrderState>): void {
    const state = publisher.getState() as unknown as OrderState; // 类型断言确保结构
    console.log(`【短信通知】订单${state.orderId}状态更新为${state.stateText},更新时间:${state.updateTime}`);
  }
}

class LogisticsSyncObserver implements IObserver {
  update<OrderState>(publisher: IPublisherWithState<OrderState>): void {
    const state = publisher.getState() as unknown as OrderState;
    // 仅处理特定状态(TS自动校验state.state是否为OrderStateEnum)
    switch (state.state) {
      case OrderStateEnum.PAID:
        console.log(`【物流同步】订单${state.orderId}已付款,创建物流单...`);
        break;
      case OrderStateEnum.SHIPPED:
        console.log(`【物流同步】订单${state.orderId}已发货,快递单号:SF123456789`);
        break;
    }
  }
}

// 7. 测试流程(类型明确,无隐式错误)
const order = new OrderPublisher('20240520001');
const smsNotify = new SmsNotifyObserver();
const logisticsSync = new LogisticsSyncObserver();

order.add(smsNotify);
order.add(logisticsSync);

// 正确调用:传入OrderStateEnum枚举值
order.updateOrderState(OrderStateEnum.PAID);
// 错误调用:传入字符串(编译时直接报错)
// order.updateOrderState('paid'); // TS报错:类型“string”不能赋值给类型“OrderStateEnum”

TS实现的核心优越性总结

特性

具体体现

接口约束

IPublisher/IObserver 强制发布者/观察者实现必要方法,避免漏写(如忘写update

枚举安全

OrderStateEnum 限定订单状态取值,避免传入非法字符串(如'payed'拼写错误)

类型明确

OrderState 定义状态结构,确保观察者接收的状态格式统一,避免“属性不存在”错误

访问控制

orderId/stateprivate 修饰,防止外部直接篡改核心状态,保证数据安全

泛型复用

IObserver.update<T> 支持不同发布者(如后续扩展“商品库存发布者”),无需重写接口

通过TS的类型系统,代码在“协作开发”和“长期维护”中更稳定,编译时即可规避JS中常见的“类型隐式转换”“属性拼写错误”等问题。

4. 观察者模式(下)

1.典型场景

1. Vue响应式系统:观察者模式的经典落地

Vue“数据变、视图更”的核心依赖观察者模式,核心逻辑可概括为“监听-依赖-通知”三步:

  • 三大关键角色
    • observer(监听器/发布者):通过Object.defineProperty劫持数据属性(添加getter/setter),同时用Dep类管理订阅者;
    • watcher(订阅者):关联组件视图,渲染时记录数据依赖,数据变化时接收通知并更新视图;
    • compile(编译器):解析模板指令,初始化数据订阅、创建watcher实例。
  • 核心流程:数据初始化→observer劫持属性→组件渲染→watcher记录依赖→数据修改触发setterDep通知watcher→视图更新。
2. 高频面试题:实现Event Bus/Event Emitter

Event Bus是发布-订阅模式的典型实现(通过第三方“事件中心”解耦),是前端跨模块通信的常用方案,核心需实现4个方法:

方法名

作用

核心逻辑

on(eventName, callback)

订阅事件

为指定事件添加监听器,存入handlers映射表

emit(eventName, ...args)

发布事件

触发指定事件的所有监听器,传入参数

off(eventName, callback)

取消订阅

从事件监听器数组中移除指定回调

once(eventName, callback)

单次订阅

监听器触发一次后自动从数组中移除

  • 应用场景:Vue非父子组件通信、Node.js模块间交互、跨页面事件通知。
3. 关键区别:观察者模式 vs 发布-订阅模式

两者核心思想一致(“发布-订阅”),但耦合程度不同,可通过通俗场景区分:

  • 观察者模式:发布者直接持有订阅者集合,双方强关联(例:产品经理拉群,直接把需求发给群里的开发者);
  • 发布-订阅模式:通过第三方“事件中心”通信,发布者和订阅者完全解耦(例:产品经理把需求上传到公司平台,平台自动通知订阅该需求的开发者);
  • 选择原则:模块关联稳定(如组件内部数据与视图)用观察者模式;模块独立(如跨组件/跨模块)用发布-订阅模式。

2. 案例练习:用户关注博主的消息通知系统

场景描述

某内容平台中:

  1. 用户可关注博主,关注后会收到博主的“新文章通知”(含标题、链接);
  2. 用户可取消关注,停止接收通知;
  3. 用户可设置“只接收一次通知”(如单次查看博主直播预告)。
// 1. 实现Event Bus(发布-订阅核心)
class EventBus {
  constructor() {
    // 存储事件-监听器映射:key=事件名,value=监听器数组
    this.handlers = {};
  }

  // 订阅事件
  on(eventName, callback) {
    if (!this.handlers[eventName]) this.handlers[eventName] = [];
    this.handlers[eventName].push(callback);
  }

  // 发布事件(浅拷贝避免once移除时影响遍历)
  emit(eventName, ...args) {
    const handlers = this.handlers[eventName]?.slice() || [];
    handlers.forEach(callback => callback(...args));
  }

  // 取消订阅
  off(eventName, callback) {
    const handlers = this.handlers[eventName];
    if (!handlers) return;
    const index = handlers.indexOf(callback);
    if (index !== -1) handlers.splice(index, 1);
    if (handlers.length === 0) delete this.handlers[eventName];
  }

  // 单次订阅
  once(eventName, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(eventName, wrapper); // 触发后自动取消
    };
    this.on(eventName, wrapper);
  }
}

// 2. 业务类:博主(发布者)、用户(订阅者)
class Blogger {
  constructor(name) {
    this.name = name;
  }

  // 发布新文章(触发事件)
  publishArticle(title, link) {
    console.log(`\n${this.name}发布新文章:《${title}》`);
    notificationBus.emit('newArticle', { bloggerName: this.name, title, link });
  }
}

class User {
  constructor(name) {
    this.name = name;
    this.followCallback = null; // 存储关注监听器,用于取消
  }

  // 关注博主(订阅事件)
  follow(blogger) {
    this.followCallback = (article) => {
      console.log(`${this.name}收到通知:${article.bloggerName}的新文章《${article.title}》,链接:${article.link}`);
    };
    notificationBus.on('newArticle', this.followCallback);
    console.log(`${this.name}已关注${blogger.name}`);
  }

  // 取消关注(移除监听器)
  unfollow() {
    if (this.followCallback) {
      notificationBus.off('newArticle', this.followCallback);
      console.log(`${this.name}已取消关注`);
    }
  }

  // 单次订阅(只看一次新文章)
  watchOnce() {
    notificationBus.once('newArticle', (article) => {
      console.log(`${this.name}(单次)收到通知:${article.bloggerName}的新文章《${article.title}》`);
    });
    console.log(`${this.name}设置只接收一次新文章通知`);
  }
}

// 3. 测试
const notificationBus = new EventBus();
const techBlogger = new Blogger('前端技术君');
const user1 = new User('小明');
const user2 = new User('小红');

user1.follow(techBlogger); // 小明关注博主
user2.watchOnce(); // 小红设置单次通知

techBlogger.publishArticle('观察者模式实战', 'https://example.com/observer'); 
// 输出:小明收到通知... 小红(单次)收到通知...

techBlogger.publishArticle('TS类型体操', 'https://example.com/ts'); 
// 输出:小明收到通知...(小红已取消)

user1.unfollow(); // 小明取消关注
techBlogger.publishArticle('Vue3源码', 'https://example.com/vue3'); 
// 输出:无通知(小明已取消)

TS的核心价值是类型约束与自动提示,通过接口、泛型明确数据结构,避免运行时错误:

// 1. 定义核心类型:约束事件与参数结构
interface NotificationEvents {
  // 事件名:监听器参数类型的映射
  newArticle: (data: ArticleData) => void;
  liveStart: (data: LiveData) => void; // 扩展直播事件
}

// 文章数据接口(明确参数结构)
interface ArticleData {
  bloggerName: string;
  title: string;
  link: string;
}

// 直播数据接口(扩展场景)
interface LiveData {
  bloggerName: string;
  liveTitle: string;
  liveUrl: string;
  startTime: string;
}

// 2. 类型安全的Event Bus(泛型约束事件映射)
class EventBus<Events extends Record<string, (...args: any[]) => void>> {
  private handlers: Partial<{ [K in keyof Events]: Events[K][] }> = {};

  // 订阅:事件名只能是Events的key,监听器类型匹配
  on<K extends keyof Events>(eventName: K, callback: Events[K]): void {
    if (!this.handlers[eventName]) this.handlers[eventName] = [];
    this.handlers[eventName]!.push(callback);
  }

  // 发布:参数类型必须匹配Events中定义的监听器参数
  emit<K extends keyof Events>(eventName: K, ...args: Parameters<Events[K]>): void {
    const handlers = this.handlers[eventName]?.slice() || [];
    handlers.forEach(callback => callback(...args));
  }

  // 取消订阅:监听器类型必须匹配
  off<K extends keyof Events>(eventName: K, callback: Events[K]): void {
    const handlers = this.handlers[eventName];
    if (!handlers) return;
    const index = handlers.indexOf(callback);
    if (index !== -1) handlers.splice(index, 1);
    if (handlers.length === 0) delete this.handlers[eventName];
  }

  // 单次订阅:类型自动匹配
  once<K extends keyof Events>(eventName: K, callback: Events[K]): void {
    const wrapper: Events[K] = ((...args) => {
      callback(...args);
      this.off(eventName, wrapper);
    }) as Events[K];
    this.on(eventName, wrapper);
  }
}

// 3. 业务类(类型安全保障)
class Blogger {
  constructor(public name: string) {} // TS简化属性定义

  publishArticle(title: string, link: string): void {
    console.log(`\n${this.name}发布新文章:《${title}》`);
    // 类型校验:必须传入符合ArticleData的参数
    notificationBus.emit('newArticle', { bloggerName: this.name, title, link });
  }

  startLive(liveTitle: string, liveUrl: string, startTime: string): void {
    console.log(`\n${this.name}即将直播:《${liveTitle}》`);
    // 类型校验:参数必须符合LiveData
    notificationBus.emit('liveStart', { bloggerName: this.name, liveTitle, liveUrl, startTime });
  }
}

class User {
  private followCallback?: NotificationEvents['newArticle']; // 明确监听器类型

  constructor(public name: string) {}

  follow(blogger: Blogger): void {
    this.followCallback = (article) => {
      // 自动提示article的属性(bloggerName/title/link)
      console.log(`${this.name}收到通知:${article.bloggerName}的新文章《${article.title}》`);
    };
    notificationBus.on('newArticle', this.followCallback);
  }

  unfollow(): void {
    if (this.followCallback) {
      notificationBus.off('newArticle', this.followCallback);
    }
  }
}

// 4. 测试(TS编译期报错保障)
const notificationBus = new EventBus<NotificationEvents>();
const techBlogger = new Blogger('前端技术君');
const user1 = new User('小明');

// 正确使用:无报错
user1.follow(techBlogger);
techBlogger.publishArticle('TS实战', 'https://example.com/ts');

// 错误使用:TS编译时报错(体现优越性)
// 1. 事件名错误(不存在'newPost')
// notificationBus.emit('newPost', { title: '错误' }); 
// 2. 参数类型错误(link传数字)
// techBlogger.publishArticle('错误文章', 123); 
// 3. 访问不存在的属性
// notificationBus.on('newArticle', (article) => console.log(article.wrongKey)); 

TS实现的优越性总结

  1. 编译期排错:避免传错事件名(如'newPost')、参数类型错误(如链接传数字)、访问不存在的属性(如article.wrongKey),运行时错误提前暴露;
  2. 自动提示:输入notificationBus.emit('时,TS自动提示可选事件名('newArticle'/'liveStart');编写监听器时,自动提示参数结构(如article包含bloggerName);
  3. 可维护性:接口(ArticleData/LiveData)明确数据结构,后续修改时,TS自动检测所有依赖代码,避免漏改;泛型EventBus可复用(换OtherEvents即可支持其他业务)。

5.迭代器模式

1. 迭代器模式的核心定义

迭代器模式的核心目标是“顺序访问集合元素,且不暴露集合的内部结构”(引自《设计模式:可复用面向对象软件的基础》)。
它解决的核心问题:不同集合(如数组、类数组、自定义数据结构)的遍历逻辑不统一,开发者需适配每种集合的底层实现(如数组用forEach,类数组需手动循环),导致代码冗余且耦合度高。

2.ES6前的迭代器困境与解决方案

1 核心困境:类数组的遍历难题

ES6前,JavaScript原生仅支持数组的forEach遍历,但像document.getElementsByTagName('a')返回的类数组(NodeList) ,虽能通过索引访问元素,却没有forEach方法,需手动写for循环遍历,导致“同样是集合,遍历方式却不同”。

jQuery通过封装迭代器,提供了统一的遍历接口,支持数组、类数组、jQuery集合等多种结构,无需关心集合内部实现:

// 遍历数组
const arr = [1, 2, 3];
$.each(arr, (index, item) => console.log(`数组元素:${item}`));

// 遍历类数组(NodeList)
const aNodes = document.getElementsByTagName('a');
$.each(aNodes, (index, node) => console.log(`链接文本:${node.innerText}`));

3. ES6对迭代器的原生支持

ES6为解决“统一遍历”问题,定义了迭代器协议(Iterator Protocol) ,原生支持数组、Set、Map、String、NodeList等集合的遍历。

(1)核心机制:Symbol.iterator属性

任何集合只要具备Symbol.iterator属性(该属性是迭代器生成函数),就能生成“迭代器对象”;迭代器对象必须包含next()方法,用于获取下一个元素。

(2)next()方法的逻辑

每次调用next(),返回一个包含donevalue的对象:

  • done:布尔值,true表示遍历结束,false表示仍有元素;
  • value:当前遍历的元素(donetrue时为undefined)。
(3)for...of的底层原理

for...of是迭代器的“语法糖”,自动调用集合的Symbol.iterator生成迭代器,反复执行next()直到donetrue

const arr = [1, 2, 3];
// 等价于手动调用迭代器
const iterator = arr[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
  console.log(result.value); // 1 → 2 → 3
  result = iterator.next();
}

4. 自定义迭代器生成函数实现

若需遍历自定义集合(如“用户列表”“订单数据”),可手动实现迭代器生成函数,核心是用“状态(游标)”记录遍历进度

通过闭包保存idx(游标),确保每次调用next()时游标递增,实现遍历状态的持久化:

function createIterator(list) {
  let idx = 0; // 游标:闭包保存,避免被垃圾回收
  const len = list.length;
  return {
    next() {
      const done = idx >= len;
      const value = !done ? list[idx++] : undefined;
      return { done, value };
    }
  };
}

// 测试:遍历选手列表
const players = ['1号选手', '2号选手', '3号选手'];
const iterator = createIterator(players);
console.log(iterator.next()); // { done: false, value: '1号选手' }
console.log(iterator.next()); // { done: false, value: '2号选手' }
console.log(iterator.next()); // { done: false, value: '3号选手' }
console.log(iterator.next()); // { done: true, value: undefined }

function*(生成器)自动维护遍历状态,yield关键字相当于“暂停点”,无需手动管理游标:

function* createIterator(list) {
  for (const item of list) {
    yield item; // 每次调用next(),返回当前item并暂停
  }
}

// 测试
const iterator = createIterator(['1号选手', '2号选手', '3号选手']);
console.log(iterator.next()); // { done: false, value: '1号选手' }

迭代器模式的核心价值

  1. 统一遍历接口:无论集合是数组、类数组还是自定义结构,都能用next()for...of遍历;
  2. 解耦遍历与集合:集合内部结构修改(如从数组改为链表),遍历代码无需改动;
  3. 控制遍历过程:可手动调用next()实现“分步遍历”(如分页加载数据时,按需获取下一页元素)。

5. 案例练习:自定义购物车商品迭代器

需求:实现一个迭代器,遍历购物车中的商品,支持获取商品的idnameprice,且能统一遍历不同来源的购物车数据(如本地数组、接口返回数据)。

// 迭代器生成函数:接收商品集合,返回迭代器
function createCartIterator(products) {
  let idx = 0; // 游标:闭包保存遍历进度
  const len = products.length;

  return {
    next() {
      const done = idx >= len;
      // 未结束时返回当前商品,结束时返回undefined
      const value = !done ? products[idx++] : undefined;
      return { done, value };
    }
  };
}

// 测试:购物车商品数据(若数据格式错误,运行时才会暴露问题)
const cartProducts = [
  { id: 1, name: "无线鼠标", price: 99 },
  { id: 2, name: "机械键盘", price: 399 },
  { id: 3, name: "笔记本支架" } // 漏传price,JS不会报错,运行时可能出问题
];

// 遍历购物车
const cartIterator = createCartIterator(cartProducts);
let current = cartIterator.next();
while (!current.done) {
  const product = current.value;
  // 若product无price,console会输出"价格:undefined元"
  console.log(`商品:${product.name},ID:${product.id},价格:${product.price}元`);
  current = cartIterator.next();
}

TS通过类型约束、接口规范、编译期校验,解决JS的“运行时错误”问题,同时提供清晰的类型提示:

// 1. 定义商品类型接口:强制商品必须包含id、name、price(编译期校验数据格式)
interface Product {
  id: number;
  name: string;
  price: number; // 若漏传,TS编译时直接报错
}

// 2. 定义迭代器接口:规范迭代器结构,确保next()返回值类型明确
interface Iterator<T> {
  next(): { done: boolean; value: T | undefined };
}

// 3. 迭代器生成函数:参数必须是Product数组,返回值必须符合Iterator<Product>
function createCartIterator(products: Product[]): Iterator<Product> {
  let idx = 0;
  const len = products.length;

  return {
    // next()返回值类型严格匹配Iterator<Product>
    next(): { done: boolean; value: Product | undefined } {
      const done = idx >= len;
      const value = !done ? products[idx++] : undefined;
      return { done, value };
    }
  };
}

// 4. 测试:购物车数据(TS自动校验格式,漏传price直接报错)
const cartProducts: Product[] = [
  { id: 1, name: "无线鼠标", price: 99 },
  { id: 2, name: "机械键盘", price: 399 },
  // { id: 3, name: "笔记本支架" } → TS编译报错:Property 'price' is missing in type...
  { id: 3, name: "笔记本支架", price: 59 } // 符合Product类型,正常通过
];

// 5. 遍历:current.value有明确的Product类型提示,避免拼写错误
const cartIterator = createCartIterator(cartProducts);
let current = cartIterator.next();
while (!current.done) {
  const product = current.value;
  // TS提示product的属性(id/name/price),避免拼写错误(如写product.prcie)
  if (product) { // TS自动推断product可能为undefined,提示需判断(避免空指针)
    console.log(`商品:${product.name},ID:${product.id},价格:${product.price}元`);
  }
  current = cartIterator.next();
}

TS实现的核心优越性

  1. 编译期错误暴露:商品漏传price时,TS编译阶段直接报错,避免JS的“运行时才发现问题”;
  2. 类型提示清晰:编写product.时,IDE自动提示id/name/price,避免拼写错误(如product.prcie);
  3. 接口复用性Iterator<T>是泛型接口,可复用为Iterator<User>(用户迭代器)、Iterator<Order>(订单迭代器);
  4. 参数约束严格:迭代器生成函数仅接收Product[],避免传入非商品类型(如string[])导致的遍历错误。

完结撒花(路虽远行则将至 )

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐