设计模式-6(观察者模式与迭代器模式)
目录
2. 高频面试题:实现Event Bus/Event Emitter
3.观察者模式(上)
1. 观察者模式的核心定位
- 重要性:JS中使用频率最高、面试频率最高的设计模式,覆盖前端核心场景(如DOM事件监听、Vue/React状态管理、异步回调),是面试官重点考查的设计模式之一。
- 官方定义:定义一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象;当目标对象的状态发生变化时,会自动通知所有观察者,使它们执行对应更新逻辑。
- 别名与差异:常被称为“发布-订阅模式”(两者核心逻辑一致,细微差异在于“角色分工粒度”——观察者模式更侧重“目标与观察者的直接依赖”,发布-订阅模式多了“中间调度层”,但日常开发中可通用)。
生活案例:快速理解核心角色
文章以“产品经理拉群发需求”为案例,清晰拆解观察者模式的3个核心角色和流程:
- 角色对号入座
-
- 发布者(目标对象):韩梅梅(产品经理),负责“管理需求状态”和“通知订阅者”(拉群=添加订阅者,发需求=通知);
- 观察者(订阅者):前端李雷、后端王五、测试赵六,负责“监听需求变化”(看群消息)和“执行响应逻辑”(接需求后开发/测试);
- 目标状态:需求文档(从“无”到“有”,从“初稿”到“终稿”)。
- 核心流程:发布者维护订阅者列表 → 目标状态变化 → 发布者主动通知所有订阅者 → 订阅者接收通知并执行逻辑。
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开发!
核心要点回顾
- 角色不可缺:必须包含“发布者”(管状态+通知)和“观察者”(接通知+执行),两者通过“发布者.notify()”和“观察者.update()”建立通信;
- 解耦是核心:发布者不关心观察者“具体做什么”,只负责通知;观察者不关心发布者“状态怎么变”,只负责响应——新增观察者/发布者时无需修改原有逻辑(符合开放封闭原则);
- 状态传递:发布者通知时需传递自身(或最新状态),让观察者能主动获取最新信息(避免观察者依赖“全局状态”)。
案例练习:电商订单状态通知系统
需求场景
设计一个电商订单状态管理系统,核心逻辑:
- 发布者:订单管理器(维护订单状态:待付款→已付款→已发货→已签收);
- 观察者: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实现的核心优越性总结
|
特性 |
具体体现 |
|
接口约束 |
|
|
枚举安全 |
|
|
类型明确 |
|
|
访问控制 |
|
|
泛型复用 |
|
通过TS的类型系统,代码在“协作开发”和“长期维护”中更稳定,编译时即可规避JS中常见的“类型隐式转换”“属性拼写错误”等问题。
4. 观察者模式(下)
1.典型场景
1. Vue响应式系统:观察者模式的经典落地
Vue“数据变、视图更”的核心依赖观察者模式,核心逻辑可概括为“监听-依赖-通知”三步:
- 三大关键角色:
-
observer(监听器/发布者):通过Object.defineProperty劫持数据属性(添加getter/setter),同时用Dep类管理订阅者;watcher(订阅者):关联组件视图,渲染时记录数据依赖,数据变化时接收通知并更新视图;compile(编译器):解析模板指令,初始化数据订阅、创建watcher实例。
- 核心流程:数据初始化→
observer劫持属性→组件渲染→watcher记录依赖→数据修改触发setter→Dep通知watcher→视图更新。
2. 高频面试题:实现Event Bus/Event Emitter
Event Bus是发布-订阅模式的典型实现(通过第三方“事件中心”解耦),是前端跨模块通信的常用方案,核心需实现4个方法:
|
方法名 |
作用 |
核心逻辑 |
|
|
订阅事件 |
为指定事件添加监听器,存入 |
|
|
发布事件 |
触发指定事件的所有监听器,传入参数 |
|
|
取消订阅 |
从事件监听器数组中移除指定回调 |
|
|
单次订阅 |
监听器触发一次后自动从数组中移除 |
- 应用场景:Vue非父子组件通信、Node.js模块间交互、跨页面事件通知。
3. 关键区别:观察者模式 vs 发布-订阅模式
两者核心思想一致(“发布-订阅”),但耦合程度不同,可通过通俗场景区分:
- 观察者模式:发布者直接持有订阅者集合,双方强关联(例:产品经理拉群,直接把需求发给群里的开发者);
- 发布-订阅模式:通过第三方“事件中心”通信,发布者和订阅者完全解耦(例:产品经理把需求上传到公司平台,平台自动通知订阅该需求的开发者);
- 选择原则:模块关联稳定(如组件内部数据与视图)用观察者模式;模块独立(如跨组件/跨模块)用发布-订阅模式。
2. 案例练习:用户关注博主的消息通知系统
场景描述
某内容平台中:
- 用户可关注博主,关注后会收到博主的“新文章通知”(含标题、链接);
- 用户可取消关注,停止接收通知;
- 用户可设置“只接收一次通知”(如单次查看博主直播预告)。
// 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实现的优越性总结
- 编译期排错:避免传错事件名(如
'newPost')、参数类型错误(如链接传数字)、访问不存在的属性(如article.wrongKey),运行时错误提前暴露; - 自动提示:输入
notificationBus.emit('时,TS自动提示可选事件名('newArticle'/'liveStart');编写监听器时,自动提示参数结构(如article包含bloggerName); - 可维护性:接口(
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(),返回一个包含done和value的对象:
done:布尔值,true表示遍历结束,false表示仍有元素;value:当前遍历的元素(done为true时为undefined)。
(3)for...of的底层原理
for...of是迭代器的“语法糖”,自动调用集合的Symbol.iterator生成迭代器,反复执行next()直到done为true:
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号选手' }
迭代器模式的核心价值
- 统一遍历接口:无论集合是数组、类数组还是自定义结构,都能用
next()或for...of遍历; - 解耦遍历与集合:集合内部结构修改(如从数组改为链表),遍历代码无需改动;
- 控制遍历过程:可手动调用
next()实现“分步遍历”(如分页加载数据时,按需获取下一页元素)。
5. 案例练习:自定义购物车商品迭代器
需求:实现一个迭代器,遍历购物车中的商品,支持获取商品的id、name、price,且能统一遍历不同来源的购物车数据(如本地数组、接口返回数据)。
// 迭代器生成函数:接收商品集合,返回迭代器
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实现的核心优越性
- 编译期错误暴露:商品漏传
price时,TS编译阶段直接报错,避免JS的“运行时才发现问题”; - 类型提示清晰:编写
product.时,IDE自动提示id/name/price,避免拼写错误(如product.prcie); - 接口复用性:
Iterator<T>是泛型接口,可复用为Iterator<User>(用户迭代器)、Iterator<Order>(订单迭代器); - 参数约束严格:迭代器生成函数仅接收
Product[],避免传入非商品类型(如string[])导致的遍历错误。
完结撒花(路虽远行则将至 )
更多推荐
所有评论(0)