迭代器接口

提供给for/of循环的对象预计为可迭代对象(iterable)。 这意味着它有一个以Symbol.iterator符号命名的方法(由语言定义的符号值,存储为Symbol符号的一个属性)。

当被调用时,该方法应该返回一个对象,它提供第二个接口迭代器(iterator)。 这是执行迭代的实际事物。 它拥有返回下一个结果的next方法。 这个结果应该是一个对象,如果有下一个值,value属性会提供它;没有更多结果时,done属性应该为true,否则为false

请注意,nextvaluedone属性名称是纯字符串,而不是符号。 只有Symbol.iterator是一个实际的符号,它可能被添加到不同的大量对象中。

我们可以直接使用这个接口。

  1. let okIterator = "OK"[Symbol.iterator]();
  2. console.log(okIterator.next());
  3. // → {value: "O", done: false}
  4. console.log(okIterator.next());
  5. // → {value: "K", done: false}
  6. console.log(okIterator.next());
  7. // → {value: undefined, done: true}

我们来实现一个可迭代的数据结构。 我们将构建一个matrix类,充当一个二维数组。

  1. class Matrix {
  2. constructor(width, height, element = (x, y) => undefined) {
  3. this.width = width;
  4. this.height = height;
  5. this.content = [];
  6. for (let y = 0; y < height; y++) {
  7. for (let x = 0; x < width; x++) {
  8. this.content[y * width + x] = element(x, y);
  9. }
  10. }
  11. }
  12. get(x, y) {
  13. return this.content[y * this.width + x];
  14. }
  15. set(x, y, value) {
  16. this.content[y * this.width + x] = value;
  17. }
  18. }

该类将其内容存储在width × height个元素的单个数组中。 元素是按行存储的,因此,例如,第五行中的第三个元素存储在位置4 × width + 2中(使用基于零的索引)。

构造器需要宽度,高度和一个可选的内容函数,用来填充初始值。 getset方法用于检索和更新矩阵中的元素。

遍历矩阵时,通常对元素的位置以及元素本身感兴趣,所以我们会让迭代器产生具有xyvalue属性的对象。

  1. class MatrixIterator {
  2. constructor(matrix) {
  3. this.x = 0;
  4. this.y = 0;
  5. this.matrix = matrix;
  6. }
  7. next() {
  8. if (this.y == this.matrix.height) return {done: true};
  9. let value = {x: this.x,
  10. y: this.y,
  11. value: this.matrix.get(this.x, this.y)};
  12. this.x++;
  13. if (this.x == this.matrix.width) {
  14. this.x = 0;
  15. this.y++;
  16. }
  17. return {value, done: false};
  18. }
  19. }

这个类在其xy属性中跟踪遍历矩阵的进度。 next方法最开始检查是否到达矩阵的底部。 如果没有,则首先创建保存当前值的对象,之后更新其位置,如有必要则移至下一行。

让我们使Matrix类可迭代。 在本书中,我会偶尔使用事后的原型操作来为类添加方法,以便单个代码段保持较小且独立。 在一个正常的程序中,不需要将代码分成小块,而是直接在class中声明这些方法。

  1. Matrix.prototype[Symbol.iterator] = function() {
  2. return new MatrixIterator(this);
  3. };

现在我们可以用for/of来遍历一个矩阵。

  1. let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
  2. for (let {x, y, value} of matrix) {
  3. console.log(x, y, value);
  4. }
  5. // → 0 0 value 0,0
  6. // → 1 0 value 1,0
  7. // → 0 1 value 0,1
  8. // → 1 1 value 1,1