迭代器接口
提供给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
