摘要:在本教程中,您将学习 JavaScript 迭代器,以及如何使用迭代器更有效地处理数据序列。
for 循环问题
当您有一个包含数据的 数组 时,通常会使用 for
循环来遍历其元素。例如
let ranks = ['A', 'B', 'C'];
for (let i = 0; i < ranks.length; i++) {
console.log(ranks[i]);
}
Code language: JavaScript (javascript)
for
循环使用变量 i
来跟踪 ranks
数组的索引。只要 i
的值小于 ranks
数组中的元素数量,i
的值就会在每次循环执行时递增。
这段代码非常简单。但是,当您在一个循环内嵌套另一个循环时,它的复杂性就会增加。此外,跟踪循环内部的多个变量很容易出错。
ES6 引入了一个名为 for...of
的新循环结构,以消除标准循环的复杂性,并避免因跟踪循环索引而导致的错误。
要遍历 ranks
数组的元素,您可以使用以下 for...of
结构
for(let rank of ranks) {
console.log(rank);
}
Code language: JavaScript (javascript)
for...of
比 for
循环更优雅,因为它显示了代码的真实意图——遍历数组以访问序列中的每个元素。
最重要的是,for...of
循环能够遍历任何可迭代对象,而不仅仅是数组。
要理解可迭代对象,您需要先了解迭代协议。
迭代协议
有两个迭代协议:可迭代协议和迭代器协议。
迭代器协议
当一个对象实现了能够回答两个问题的接口(或 API)时,它就是一个迭代器
- 还有其他元素吗?
- 如果有,元素是什么?
从技术上讲,当一个对象具有 next()
方法时,它就被认为是迭代器,该方法返回一个具有两个属性的对象
-
done
:一个布尔值,指示是否还有可以迭代的元素。 -
value
:当前元素。
每次调用 next()
时,它都会返回集合中的下一个值
{ value: 'next value', done: false }
Code language: CSS (css)
如果您在返回最后一个值后调用 next()
方法,则 next()
会返回以下结果对象
{done: true: value: undefined}
Code language: CSS (css)
done
属性的值表示没有更多值可返回,value
属性的值设置为 undefined
。
可迭代协议
当一个对象包含一个名为 [Symbol.iterator]
的方法时,它就是可迭代的,该方法不接受任何参数,并返回一个符合迭代器协议的对象。
[Symbol.iterator]
是 ES6 中内置的众所周知的 符号 之一。
迭代器
由于 ES6 为集合类型提供了内置迭代器,例如 数组
、Set
和 Map
,因此您不必为这些对象创建迭代器。
如果您有一个自定义类型,并且希望使其可迭代,以便您可以使用 for...of
循环结构,那么您需要实现迭代协议。
以下代码创建一个 Sequence
对象,该对象返回 (start
, end
) 范围内的一系列数字,后续数字之间有 interval
间隔。
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
}
}
}
};
Code language: JavaScript (javascript)
以下代码在 for...of
循环中使用 Sequence
迭代器
let evenNumbers = new Sequence(2, 10, 2);
for (const num of evenNumbers) {
console.log(num);
}
Code language: JavaScript (javascript)
输出
2
4
6
8
10
您可以像以下脚本所示那样显式地访问 [Symbol.iterator]()
方法
let evenNumbers = new Sequence(2, 10, 2);
let iterator = evenNumbers[Symbol.iterator]();
let result = iterator.next();
while( !result.done ) {
console.log(result.value);
result = iterator.next();
}
Code language: JavaScript (javascript)
清理
除了 next()
方法之外,[Symbol.iterator]()
还可以可选地返回一个名为 return()
的方法。
当迭代提前停止时,return()
方法会自动调用。您可以在其中放置代码来清理资源。
以下示例为 Sequence
对象实现了 return()
方法
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex <= this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
},
return: () => {
console.log('cleaning up...');
return { value: undefined, done: true };
}
}
}
}
Code language: JavaScript (javascript)
以下代码片段使用 Sequence
对象生成从 1 到 10 的一系列奇数。但是,它提前停止了迭代。因此,return()
方法会自动调用。
let oddNumbers = new Sequence(1, 10, 2);
for (const num of oddNumbers) {
if( num > 7 ) {
break;
}
console.log(num);
}
Code language: JavaScript (javascript)
输出
1
3
5
7
cleaning up...
在本教程中,您学习了 JavaScript 迭代器以及如何使用迭代协议来实现自定义迭代逻辑。