在本教程中,您将了解 JavaScript 迭代器以及如何使用迭代器更有效地处理数据序列。

for 循环问题

当你有一个数据数组时,你通常使用 for 循环来迭代它的元素。例如:

let ranks = ['A', 'B', 'C'];

for (let i = 0; i < ranks.length; i++) {
    console.log(ranks[i]);
}

for 循环使用变量  i 来跟踪 ranks 数组的索引。每次循环执行时,只要 i 的值小于 ranks 数组中的元素数,它的 i 值就会递增。

这段代码很简单。但是,当您将一个循环嵌套在另一个循环中时,它的复杂性会增加。此外,跟踪循环内的多个变量很容易出错。

ES6 引入一种新的循环结构称为 for...of ,来消除标准 for 循环的复杂性并避免因跟踪 for 循环索引而导致的错误。

要迭代 ranks 数组的元素,您可以使用以 for...of 循环:

for(let rank of ranks) {
    console.log(rank);
}

for...offor 循环优雅得多,因为它展示代码的真正意图并遍历数组访问每个元素。

最重要的是,for...of 循环能够在任何可迭代对象上创建一个循环,而不仅仅是一个数组。要了解可迭代对象,您需要先了解迭代协议。

迭代协议

有两种迭代协议:iterable protocol iterator protocol

迭代器协议 iterator protocol

当一个对象实现下面两个接口时,它就是一个迭代器:

  • 有没有剩下的元素?
  • 如果有,元素是什么?

从技术上讲,当一个对象有一个 next() 方法返回具有以下两个属性的对象,它就被称为迭代器:

  • done:  布尔值,指示是否还有可以迭代的元素。
  • value: 当前元素。

每次调用 next() 时,它都会返回集合中的下一个值:

{ value: 'next value', done: false }

如果在调用 next() 方法返回最后一个值,则 next() 方法返回结果对象如下:

{done: true: value: undefined}

属性 done 的值表示没有更多的值可返回,属性 value 的设置为 undefined

可迭代协议

当一个对象包含一个称为 [Symbol.iterator] 的方法,该不接受任何参数并返回一个符合迭代器协议的对象时,则该对象是可迭代的。

 [Symbol.iterator] 是 ES6 内置的众所周知的符号之一。

迭代器

由于 ES6 为集合类型 ArraySetMap 提供内置迭代器 ,因此您不必为这些对象创建迭代器。

如果您有自定义类型并希望使其可迭代可以使用 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 };
            }
        }
    }
};

以下代码在 for...of 循环中使用 Sequence 迭代器:

let evenNumbers = new Sequence(2, 10, 2);

for (const num of evenNumbers) {
    console.log(num);
}

输出:

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();
}

迭代器 return 方法

除了 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 };
            }
        }
    }
}

以下代码段使用 Sequence 对象生成从 1 到 10 的奇数序列。但是,它过早地停止迭代。最后,return() 方法被自动调用。

let oddNumbers = new Sequence(1, 10, 2);

for (const num of oddNumbers) {
    if( num > 7 ) {
        break;
    }
    console.log(num);
}

输出:

1
3
5
7
cleaning up...

结论

在本教程中,您了解了 JavaScript 迭代器以及如何使用迭代协议来实现自定义迭代逻辑。