迭代器接口
提供给for/of
循环的对象预计为可迭代对象(iterable)。 这意味着它有一个以Symbol.iterator
符号命名的方法(由语言定义的符号值,存储为Symbol
符号的一个属性)。
当被调用时,该方法应该返回一个对象,它提供第二个接口迭代器(iterator)。 这是执行迭代的实际事物。 它拥有返回下一个结果的next
方法。 这个结果应该是一个对象,如果有下一个值,value
属性会提供它;没有更多结果时,done
属性应该为true
,否则为false
。
请注意,next
,value
和done
属性名称是纯字符串,而不是符号。 只有Symbol.iterator
是一个实际的符号,它可能被添加到不同的大量对象中。
我们可以直接使用这个接口。
let okIterator = "OK"[Symbol.iterator]();
console.log(okIterator.next());
// → {value: "O", done: false}
console.log(okIterator.next());
// → {value: "K", done: false}
console.log(okIterator.next());
// → {value: undefined, done: true}
我们来实现一个可迭代的数据结构。 我们将构建一个matrix
类,充当一个二维数组。
class Matrix {
constructor(width, height, element = (x, y) => undefined) {
this.width = width;
this.height = height;
this.content = [];
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
this.content[y * width + x] = element(x, y);
}
}
}
get(x, y) {
return this.content[y * this.width + x];
}
set(x, y, value) {
this.content[y * this.width + x] = value;
}
}
该类将其内容存储在width × height
个元素的单个数组中。 元素是按行存储的,因此,例如,第五行中的第三个元素存储在位置4 × width + 2
中(使用基于零的索引)。
构造器需要宽度,高度和一个可选的内容函数,用来填充初始值。 get
和set
方法用于检索和更新矩阵中的元素。
遍历矩阵时,通常对元素的位置以及元素本身感兴趣,所以我们会让迭代器产生具有x
,y
和value
属性的对象。
class MatrixIterator {
constructor(matrix) {
this.x = 0;
this.y = 0;
this.matrix = matrix;
}
next() {
if (this.y == this.matrix.height) return {done: true};
let value = {x: this.x,
y: this.y,
value: this.matrix.get(this.x, this.y)};
this.x++;
if (this.x == this.matrix.width) {
this.x = 0;
this.y++;
}
return {value, done: false};
}
}
这个类在其x
和y
属性中跟踪遍历矩阵的进度。 next
方法最开始检查是否到达矩阵的底部。 如果没有,则首先创建保存当前值的对象,之后更新其位置,如有必要则移至下一行。
让我们使Matrix
类可迭代。 在本书中,我会偶尔使用事后的原型操作来为类添加方法,以便单个代码段保持较小且独立。 在一个正常的程序中,不需要将代码分成小块,而是直接在class
中声明这些方法。
Matrix.prototype[Symbol.iterator] = function() {
return new MatrixIterator(this);
};
现在我们可以用for/of
来遍历一个矩阵。
let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`);
for (let {x, y, value} of matrix) {
console.log(x, y, value);
}
// → 0 0 value 0,0
// → 1 0 value 1,0
// → 0 1 value 0,1
// → 1 1 value 1,1