迭代器模式

用点钞机的思路理解迭代器模式的实现

问题

什么是迭代器模式?如何用”点钞机”的思路来理解它?

解答

迭代器模式提供一种方法,顺序访问集合中的元素,而不暴露集合的内部结构。

就像银行的点钞机:

  • 不关心钱是怎么放的(内部结构)
  • 只需要一张一张取出来数(遍历)
  • 每次取一张,直到没有为止

基本实现

// 创建一个"点钞机"迭代器
function createMoneyCounter(bills) {
  let index = 0;
  
  return {
    // 取下一张
    next() {
      if (index < bills.length) {
        return { value: bills[index++], done: false };
      }
      return { value: undefined, done: true };
    },
    // 是否还有钱
    hasNext() {
      return index < bills.length;
    }
  };
}

// 使用
const bills = [100, 50, 50, 20, 10];
const counter = createMoneyCounter(bills);

console.log(counter.next()); // { value: 100, done: false }
console.log(counter.next()); // { value: 50, done: false }
console.log(counter.next()); // { value: 50, done: false }

实现 JavaScript 迭代器协议

// 钱包类,实现迭代器协议
class Wallet {
  constructor() {
    this.bills = [];
  }
  
  add(bill) {
    this.bills.push(bill);
  }
  
  // 实现 Symbol.iterator,让钱包可以被 for...of 遍历
  [Symbol.iterator]() {
    let index = 0;
    const bills = this.bills;
    
    return {
      next() {
        if (index < bills.length) {
          return { value: bills[index++], done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
}

// 使用
const wallet = new Wallet();
wallet.add(100);
wallet.add(50);
wallet.add(20);

// 可以用 for...of 遍历
for (const bill of wallet) {
  console.log(`数到一张 ${bill} 元`);
}

// 也可以用展开运算符
const allBills = [...wallet];
console.log(allBills); // [100, 50, 20]

用生成器简化实现

class Wallet {
  constructor() {
    this.bills = [];
  }
  
  add(bill) {
    this.bills.push(bill);
  }
  
  // 生成器函数自动返回迭代器
  *[Symbol.iterator]() {
    for (const bill of this.bills) {
      yield bill;
    }
  }
}

// 使用方式完全相同
const wallet = new Wallet();
wallet.add(100);
wallet.add(50);

for (const bill of wallet) {
  console.log(bill);
}

实际应用:遍历树结构

class TreeNode {
  constructor(value) {
    this.value = value;
    this.children = [];
  }
  
  add(child) {
    this.children.push(child);
    return this;
  }
  
  // 深度优先遍历
  *[Symbol.iterator]() {
    yield this.value;
    for (const child of this.children) {
      yield* child; // yield* 委托给子节点的迭代器
    }
  }
}

// 构建树
const root = new TreeNode('A');
const b = new TreeNode('B');
const c = new TreeNode('C');

root.add(b).add(c);
b.add(new TreeNode('D')).add(new TreeNode('E'));

// 遍历整棵树
console.log([...root]); // ['A', 'B', 'D', 'E', 'C']

关键点

  • 迭代器将遍历逻辑与数据结构分离,调用者不需要知道内部实现
  • JavaScript 迭代器协议:实现 Symbol.iterator 方法,返回包含 next() 的对象
  • next() 返回 { value, done } 格式的对象
  • 生成器函数(function*)配合 yield 可以简化迭代器实现
  • 实现迭代器后,可使用 for...of、展开运算符、Array.from() 等语法