【06】前端常用的设计模式之迭代器模式

65 阅读2分钟

迭代器模式

概念

用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

for 循环不是迭代器模式

简单的 for 循环并不是迭代器模式,因为 for 循环需要知道对象内部结构。

// 要知道数组的长度
// 要知道通过 arr[i] 形式得到item
const arr = [100, 20, 30];
const len = arr.length;
for (let i = 0; i < len; i++) {
  console.log(arr[i]);
}

简易迭代器

有些对象,并不知道他的内部结构

// 不知道长度
// 不知道如何获取 item
const pList = document.querySelectAll('p');
pList.forEach((p) => console.log(p));

forEach 就是最简单的迭代器

UML 类图

企业微信截图_eb8cbfb1-128a-406a-bd8f-640b8f29a5fc.png

代码演示

class DataIterator {
  private data: number[];
  private index = 0;

  constructor(container: DataContainer) {
    this.data = container.data;
  }

  next(): number | null {
    if (this.hasNext()) {
      return this.data[this.index++];
    }
    return null;
  }

  hasNext() {
    if (this.index >= this.data.length) return false;
    return true;
  }
}

class DataContainer {
  data: number[] = [10, 20, 30];
  getIterator() {
    return new DataIterator(this);
  }
}

const container = new DataContainer();
const iterator = container.getIterator();
while (iterator.hasNext()) {
  const num = iterator.next();
  console.log(num);
}

场景

JS 有序对象,都内置迭代器

  • 字符串
  • 数组
  • NodeList 等 DOM 集合
  • Map
  • Set
  • arguments

【注意】对象 object 不是有序结构。

Symbol.iterator

每个有序对象,都内置了 Symbol.iterator 属性,属性值是一个函数。 执行该函数将返回 iterator 迭代器,有 next() 方法,执行返回 { value, done } 结构。

// 数组举例
const arr = [1000, 20, 30];
const iterator = arr[Symbol.iterator]();

iterator.next(); // {value: 1000, done: false}
iterator.next(); // {value: 20, done: false}
iterator.next(); // {value: 30, done: false}
iterator.next(); // {value: undefined, done: true}

另外,有些对象的 API 也会生成有序对象

const map = new Map([
  ['k1', 'v1'],
  ['k2', 'v2'],
]);
const mapIterator = map[Symbol.iterator]();

const values = map.values();
const valuesIerator = values[Symbol.iterator]();
// 还有 keys entries

自定义迭代器

// demo06/iterator.ts
// 自定义迭代器
interface IteratorRes {
  value: number | undefined;
  done: boolean;
}

class CustomIterator {
  private length = 3;
  private index = 0;

  next(): IteratorRes {
    this.index++;
    if (this.index <= this.length) {
      return { value: this.index, done: false };
    }
    return { value: undefined, done: true };
  }

  [Symbol.iterator]() {
    return this;
  }
}

const iterator = new CustomIterator();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

export {};
// demo06/yield.ts
class CustomIterator {
  private data: number[];

  constructor() {
    this.data = [100, 20, 30];
  }

  *[Symbol.iterator]() {
    yield* this.data;
  }
}

const iterator = new CustomIterator();
for (const n of iterator) {
  console.log(n);
}

export {};

有序结构的作用

for...of

所有有序结构,都支持 for...of 语法

数组操作

// 数组解构
const someList = [10, 20];
const [n1, n2] = someList;

// 拓展操作符
const arr = [...someList];

// Array.from()
const arr = Array.from(someList);

创建 Map 和 Set

const map = new Map([
  ['k1', 'v1'],
  ['k2', 'v2'],
]);
const set = new Set(someList);

Promise.all 和 Promise.race

Promise.all([promise1, promise2, promise3]);
Promise.race([promise1, promise2, promise3]);

Generator 生成器

yield* 操作符

function* genNums() {
  yield 10;
  yield 20;
  yield 30;
}

const numsIterator = genNums(); // 迭代器,如 arr[Symbol.iterator]()
console.log(numsIterator.next());
for (let n of numsIterator) {
  console.log(n);
}
function* genNums() {
  yield* [10, 20, 30]; // 有序结构,已经实现了 Symbol.iterator
  // => [10,20,30].forEach(n => yield n);
}

const numsIterator = genNums();
for (let n of numsIterator) {
  console.log(n);
}

使用 Generator 遍历 DOM 树

function* traverse(elemList: Element[]): any {
  for (const elem of elemList) {
    yield elem;

    const children = Array.from(elem.children);
    if (children.length) {
      yield* traverse(children);
    }
  }
}

const container = document.getElementById('container');
if (container) {
  for (let node of traverse([container])) {
    console.log(node);
  }
}

设计原则验证

  • 使用者和目标数据隔离,解耦
  • 目标数据自行控制内部迭代逻辑
  • 使用者不关心目标数据的内部结构
  • 符合开放封闭原则