JavaScript 迭代器终极指南

摘要:在本教程中,您将学习 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...offor 循环更优雅,因为它显示了代码的真实意图——遍历数组以访问序列中的每个元素。

最重要的是,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 为集合类型提供了内置迭代器,例如 数组SetMap,因此您不必为这些对象创建迭代器。

如果您有一个自定义类型,并且希望使其可迭代,以便您可以使用 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 迭代器以及如何使用迭代协议来实现自定义迭代逻辑。

本教程对您有帮助吗?