理解 JavaScript 中的迭代器、可迭代对象、生成器以及异步迭代

419 阅读3分钟

理解 JavaScript 中的迭代器、可迭代对象、生成器以及异步迭代

在现代 JavaScript 开发中,迭代器和可迭代对象是非常重要的概念,它们为我们提供了一种统一的方式来遍历数据结构。与此相关的是生成器,它们使得我们可以更灵活地控制函数的执行。

一、迭代器和可迭代对象

1.1 可迭代对象

在 JavaScript 中,任何实现了 Symbol.iterator 方法的对象都被称为可迭代对象。常见的可迭代对象包括数组、字符串、Map、Set 等。可迭代对象可以使用 for-of 循环直接遍历。

const arr = [1, 2, 3];
for (const value of arr) {
    console.log(value); // 输出 1, 2, 3
}

1.2 迭代器

迭代器是一个实现了 next() 方法的对象,next() 方法返回一个包含两个属性的对象:

  • value: 返回当前迭代的值
  • done: 一个布尔值,表示迭代是否完成

我们可以手动创建一个迭代器:

function createIterator(array) {
    let index = 0;
    return {
        next() {
            if (index < array.length) {
                return { value: array[index++], done: false };
            } else {
                return { done: true };
            }
        }
    };
}

const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { done: true }

二、生成器

生成器是特殊类型的函数,可以通过 function* 关键字定义。生成器函数返回一个迭代器对象,调用 next() 方法会执行生成器函数,直到遇到 yield 语句。

function* generator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = generator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { done: true }

生成器让我们能够在函数执行过程中暂停和恢复状态,适用于处理异步数据流和复杂迭代逻辑。

三、使用 for-of 循环

for-of 循环可以用来遍历任何可迭代对象,包括数组、字符串、Set 和 Map。其语法简单优雅,使得对可迭代对象的遍历变得直观。

const iterable = [10, 20, 30];
for (const value of iterable) {
    console.log(value); // 输出 10, 20, 30
}

3.1 在对象中使用 for-of

为了使对象可用于 for-of 循环,我们需要为其实现 Symbol.iterator 方法。

const obj = {
    values: [1, 2, 3],
    [Symbol.iterator]() {
        let index = 0;
        return {
            next: () => ({
                value: this.values[index++],
                done: index > this.values.length
            })
        }
    }
};

for (const value of obj) {
    console.log(value); // 输出 1, 2, 3
}

// 也可以通过调用迭代器手动遍历
// const iterator = obj[Symbol.iterator]();
// let result = iterator.next();
// while (!result.done) {
//     console.log(result.value);
//     result = iterator.next();
// }

四、异步迭代:for-await-of

在处理异步数据流(如网络请求、数据库查询等)时,for-await-of 是一种非常实用的语法。它允许我们等待异步迭代器的每一项。

4.1 创建异步迭代器

async function* asyncGenerator() {
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    for (let i = 1; i <= 3; i++) {
        await delay(1000); // 每次等待 1 秒
        yield i;
    }
}

(async () => {
    for await (const value of asyncGenerator()) {
        console.log(value); // 输出 1, 2, 3 (每次间隔 1 秒)
    }
})();

五、结合 Promise 和异步迭代

异步迭代器在处理 Promise 时特别有用。它们使得以更可读的方式处理多个异步操作变得更容易。

5.1 示例:处理异步 Promise

const fetchData = (id) => {
    return new Promise((resolve) => {
        setTimeout(() => resolve(`Data ${id}`), 1000);
    });
};

async function* fetchAsyncData() {
    for (let id = 1; id <= 3; id++) {
        yield fetchData(id);
    }
}

(async () => {
    for await (const data of fetchAsyncData()) {
        console.log(data); // 输出 Data 1, Data 2, Data 3 (每次间隔 1 秒)
    }
})();

结论

通过理解迭代器、可迭代对象、生成器以及异步迭代,我们能够更灵活和高效地处理数据集。迭代器提供了一种统一的遍历接口,而生成器及异步迭代器使得处理状态和异步操作变得更为简单。