JavaScript 迭代器使用众所周知的符号 Symbol.iterator 来定义一个函数,该函数在调用时会返回一个对象。该对象有一个方法next,它返回另一个具有两个属性的对象:value(迭代器的当前值)和done(迭代器是否完成)。示例:
const foo = {};
foo[Symbol.iterator] = function* () {
let i = 0;
yield ++i;
};
let iter = foo[Symbol.iterator]();
iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
注意* 表示generator function。
迭代器也可以与for..of 等自动构造一起使用。然而,我在上面定义的那个不适用于for..of,因为它永远不会完成:循环遍历它的所有属性最终会导致内存不足。所以让我们使用一个数组:
const bar = [1,2];
for (let baz of bar) console.log(baz); // logs 1, 2, stops
所有内置集合(除了弱集合)都可以通过这种方式迭代,因为它们实现了这个接口。
如果调用者在它们被耗尽之前完成了对它们的处理,则可能会显式关闭保留像文件句柄之类的资源的可迭代对象。例如,我们将使用带有readFromFile 和getNextLine 函数的假设文件API:
const fileReader = {
[Symbol.iterator] (path) {
return {
fileHandle: readFromFile(path),
next () {
return {
value: this.fileHandle.getNextLine(),
done: false
}
}
return () {
this.fileHandle.close();
}
}
}
};
您可以调用return 方法来防止泄漏文件句柄。请参阅 Reg Braithwaite 的this post 了解更多信息。另请注意,由于我们明确定义了方法返回的迭代器,因此我们不需要将其设为生成器函数。
如果您的迭代器是生成器函数,如我上面的无限计数列表中的那样,您可以通过yield 关键字pass it values:
foo[Symbol.iterator] = function* () {
let i = 0;
let reset = yield ++i;
if (reset) i = 0;
};
iter = foo[Symbol.iterator]();
iter.next().value; // 1
iter.next().value // 2
iter.next(true).value // 1 again
假设您有一些对象myObject,您希望能够迭代其属性。对于下一个示例,我们将忽略 Object.entries:
const myObject = {
a: 1,
b: 2
};
Object.defineProperty(myObject, Symbol.iterator, {
value: function* () {
for (let key in myObject) {
if (myObject.hasOwnProperty(key)) {
yield [key, myObject[key]];
}
}
}
});
iter = myObject[Symbol.iterator]();
iter.next(); // { value: ['a', 1], done: false }
iter.next(); // { value: ['b', 2], done: false }
iter.next(); // { value: undefined, done: true }
注意
在 ES 2015 之前,不保证对象属性有顺序。如果您在最新的 Chrome/FF/Safari 中运行上述代码,则一切正常,但如果您将其转换为旧版浏览器,您可能会首先看到 ['b', 2] 输出,或者不会在每次迭代时更改。
现在您对 JavaScript 可迭代对象的了解可能比您真正想要的要多。