迭代器模式

141 阅读3分钟

迭代器模式

迭代器模式描述了一个方案,即可以把有些数据结构成为“可迭代对象”,因为它们实现了正式的 Iterable 接口,而且可以通过迭代器 Iterator 消费

可迭代对象是一种抽象的说法,基本上可以将可迭代对象理解成,数组或者集合这样的集合类型的对象,他们包含的元素都是有限的,而且都是无歧义的遍历顺序

迭代器是按需创建的一次性对象,每个迭代器都会关联一个可迭代对象,而迭代器会暴露迭代其关联的可迭代对象的 API

可迭代协议

实现 Iterable 接口(可迭代协议)要求同时具备两种能力:支持迭代的自我时被能力和创建 Iterator 接口的对象的能力。这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须和四用特殊的 Symbol.iterator 作为键,这个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器

let str = "abc";
console.log(str[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }

// 调用默认的工厂函数,会返回一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator{}

实现可迭代协议的所有类型都会自动兼容接收可迭代对象的任何语言特性,接收可迭代对象的原生语言特性包括:

  • for-of 循环
  • 数组结构
  • 扩展操作符
  • Array.from()
  • 创建集合
  • 创建映射
  • Promise.all() 接收由期约组成的可迭起对象
  • Promisr.race() 接收由期约组成的可迭代对象
  • yidle* 操作符,在生成器中使用

迭代器协议

迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。迭代器 API 使用 next() 方法在可迭代迭代对象中遍历数据。每次成功调用 next(), 都会返回一个 IteratorResult 对象,其中包含迭代器返回的下一个值。

next() 方法返回的迭代器对象 IteratorResult 包含两个属性: donevaluedone 一个布尔值,表示是否还可再次调用 next() 取得下一个值, value 包含可迭代对象的下一个值(donefalse),或者 undefineddonetrue

let arr = ["foo", "bar"];

let iter = arr[Symbol.iterator]();

console.log(iter.next()); // {value: 'foo', done: false}
console.log(iter.next()); // {value: 'bar', done: false}
console.log(iter.next()); // {value: undefined, done: true}

注意: 迭代器维护这一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象

自定义迭代器

任何实现 Iterator 接口的对象都可以作为迭代器使用。

class Counter {
  constructor(limit) {
    this.count = 1;
    this.limit = limit;
  }

  next() {
    if (this.count <= this.limit) {
      return { done: false, value: this.count++ };
    } else {
      return { done: true, value: undefined };
    }
  }

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

let counter = new Counter(3);

for (let i of counter) {
  console.log(i);
}

上面的例子中,每个对象实例只能被迭代一次,为了让一个对象能够创建多个叠第七,必须每创建一个迭代器就对应一个新的计数器。

class Counter {
  constructor(limit) {
    this.count = 1;
    this.limit = limit;
  }

  [Symbol.iterator]() {
    /**
     * 把计数器放进闭包里,然后通过闭包返回迭代器
     */
    let count = 1,
      limit = this.limit;
    return {
      next() {
        if (this.count <= this.limit) {
          return { done: false, value: this.count++ };
        } else {
          return { done: true, value: undefined };
        }
      },
    };
  }
}

let counter = new Counter(3);

for (let i of counter) {
  console.log(i);
}

提前终止迭代器

执行迭代的结构可能会在没有遍历完成的情况下退出,此时需要关闭迭代器。例如:

  • for-of 循环中的 breakcontinuereturn 或者 throw
  • 结构操作并为消费所有值

return() 方法必须返回一个有项的 IteratorResult 对象

class Counter {
  constructor(limit) {
    this.count = 1;
    this.limit = limit;
  }

  [Symbol.iterator]() {
    /**
     * 把计数器放进闭包里,然后通过闭包返回迭代器
     */
    let count = 1,
      limit = this.limit;
    return {
      next() {
        if (this.count <= this.limit) {
          return { done: false, value: this.count++ };
        } else {
          return { done: true, value: undefined };
        }
      },
      return() {
        console.log("Exiting early");
        return { done: true };
      },
    };
  }
}

let counter = new Counter(5);

for (let i of counter) {
  if (i > 2) {
    break;
  }
  console.log(i);
}

// 1
// 2
// Exiting early

如果迭代器没有关闭,则可以继续从上次离开的地方继续迭代,例如,数组的迭代器就是不能关闭的

let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
for (let i of iter) {
  console.log(i);
  if (i > 2) {
    break;
  }
}
// 1
// 2
// 3
for (let i of iter) {
  console.log(i);
}
// 4
// 5