在 JavaScript ES6 中,可迭代和迭代器有什么区别?

nop*_*ole 13 javascript iterator iterable ecmascript-6

迭代器与迭代器相同还是不同?

从规范来看,iterable似乎是一个对象,obj例如,obj[Symbol.iterator]引用一个函数,以便在调用时返回一个具有next可以返回{value: ___, done: ___}对象的方法的对象:

function foo() {
    let i = 0;
    const wah = {
        next: function() {
            if (i <= 2) return { value: (1 + 2 * i++), done: false }
            else return { value: undefined, done: true }
        }
    };
    return wah;     // wah is iterator
}

let bar = {}        // bar is iterable

bar[Symbol.iterator] = foo;

console.log([...bar]);             // [1, 3, 5]   
for (a of bar) console.log(a);     // 1 3 5 (in three lines)
Run Code Online (Sandbox Code Playgroud)

所以在上面的代码中,bar是可迭代的,wah是迭代器,next()是迭代器的接口。

所以,iterable 和 iterator 是不同的东西。

但是,现在在生成器和迭代器的常见示例中:

function* gen1() {
    yield 1;
    yield 3;
    yield 5;
}

const iter1 = gen1();

console.log([...iter1]);                           // [1, 3, 5]
for (a of iter1) console.log(a);                   // nothing

const iter2 = gen1();
for (a of iter2) console.log(a);                   // 1 3 5 (in three lines)

console.log(iter1[Symbol.iterator]() === iter1);   // true
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,gen1是生成器,iter1是迭代器,并且iter1.next()会做适当的工作。但是iter1[Symbol.iterator]确实给出了一个函数,该函数在调用时返回iter1,它是一个迭代器。那么iter1在这种情况下是可迭代的还是迭代器?

此外,iter1与上面的示例1不同,因为示例1中的iterable可以[1, 3, 5]使用 using 多次给出[...bar],而虽然iter1是一个迭代器,但由于它返回自身,每次都是相同的迭代器,因此只会给出[1, 3, 5]一次。

所以我们可以说,对于一个 iterable bar,可以[...bar]给出多少次结果[1, 3, 5]——答案是,这取决于。iterable 和迭代器一样吗?答案是,它们是不同的东西,但是当可迭代对象将自身用作迭代器时,它们可以是相同的。那是对的吗?

T.J*_*der 9

是的,迭代迭代器是不同的东西,但是大多数迭代器(包括你从 JavaScript 本身获得的所有迭代器,例如来自keysvalues方法Array.prototype或来自生成器函数的生成器)继承自%IteratorPrototype% 对象,它有一个Symbol.iterator类似的方法这个:

[Symbol.iterator]() {
    return this;
}
Run Code Online (Sandbox Code Playgroud)

结果是所有标准迭代器也是可迭代的。这样你就可以直接使用它们,或者在for-of循环等中使用它们(需要迭代器,而不是迭代器)。

考虑keys数组的方法:它返回一个数组迭代器,它访问数组的键(它的索引,作为数字)。请注意,它返回一个迭代器。但它的一个常见用途是:

for (const index of someArray.keys()) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

for-of需要一个iterable,而不是一个iterator,那为什么会这样呢?

它之所以有效,是因为迭代器也是可迭代的;Symbol.iterator刚刚返回this

这是我在本书第 6 章中使用的一个示例:如果您想遍历所有条目但跳过第一个条目并且不想使用slice切掉子集,则可以获取迭代器,读取第一个值,然后交给一个for-of循环:

[Symbol.iterator]() {
    return this;
}
Run Code Online (Sandbox Code Playgroud)

请注意,这是所有标准迭代器。有时人们会展示手动编码迭代器的示例,如下所示:

for (const index of someArray.keys()) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

rangethere返回的迭代器不是可迭代的,因此当我们尝试将它与for-of.

为了使其可迭代,我们需要:

  1. Symbol.iterator上面答案开头的方法添加到其中,或者
  2. 让它从 %IteratorPrototype% 继承,它已经有那个方法

遗憾的是,TC39 决定不提供直接获取 %IteratorPrototype% 对象的方法。有一种间接的方法(从数组中获取迭代器,然后获取其原型,其定义为 %IteratorPrototype%),但这很痛苦。

但是无论如何都没有必要像那样手动编写迭代器;只需使用生成器函数,因为它返回的生成器是可迭代的:

const a = ["one", "two", "three", "four"];
const it = a[Symbol.iterator]();
// Skip the first one
it.next();
// Loop through the rest
for (const value of it) {
    console.log(value);
}
Run Code Online (Sandbox Code Playgroud)


相比之下,并非所有可迭代对象都是迭代器。数组是可迭代的,但不是迭代器。字符串、映射和集合也是如此。