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


14

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

从规范中看来,一个可迭代对象是一个对象,例如,objobj[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)

因此,在上面的代码中,bar是,wah是迭代器,next()是迭代器接口。

因此,可迭代和迭代器是不同的东西。

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

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

在上述情况下,gen1是生成器,iter1是迭代器,iter1.next()将完成适当的工作。但是iter1[Symbol.iterator]确实提供了一个函数,该函数在调用时会返回iter1,这是一个迭代器。那么iter1在这种情况下既是可迭代的又是迭代器?

此外,iter1从上面的例子1不同,因为迭代实例1中可以给[1, 3, 5]作为用想很多次[...bar],虽然iter1是一个迭代的,但由于它返回本身,这是每次迭代器一样,只会给[1, 3, 5]一次。

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



那么iter1,在这种情况下,迭代和迭代都是吗? ”-是的。所有本机迭代器也可以通过返回自身来进行迭代,因此您可以轻松地将它们传递给需要迭代的构造。
Bergi

Answers:


10

是的,iterables迭代器是不同的东西,但大多数迭代器(包括所有你从JavaScript本身得到的那些,如来自keysvalues关于方法Array.prototype或发电机的发电功能)从继承%IteratorPrototype%的对象,其中有一个Symbol.iterator类似的方法这个:

[Symbol.iterator]() {
    return this;
}

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

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

for (const index of someArray.keys()) {
    // ...
}

for-of需要一个iterable,而不是iterator,那为什么行得通呢?

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

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

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

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

range那里返回的迭代器不是可迭代的,因此当我们尝试将其与结合使用时,它将失败for-of

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

  1. Symbol.iterator在上面答案的开头添加方法,或者
  2. 使它继承自%IteratorPrototype%,后者已具有该方法

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

但是无论如何,无需像这样手动编写迭代器。只需使用一个生成器函数,因为它返回的生成器是可迭代的:


相反,并非所有可迭代的对象都是迭代器。数组是可迭代的,但不是迭代器。字符串,地图和集合也是如此。


0

我发现术语有一些更精确的定义,这些是更确定的答案:

根据ES6规范MDN

当我们有

function* foo() {   // note the "*"
    yield 1;
    yield 3;
    yield 5;
}

foo被称为生成器函数。然后当我们有

let bar = foo();

bar是一个生成器对象。和发电机对象符合可迭代协议和迭代器协议两者

较简单的版本是迭代器接口,它只是一种.next()方法。

可迭代的协议是:对于object objobj[Symbol.iterator]给出一个“零参数函数,该对象返回符合迭代器协议的对象”。

通过MDN链接标题,似乎我们也可以仅将生成器对象称为“生成器”。

请注意,在尼古拉斯·扎卡斯(Nicolas Zakas)的《理解ECMAScript 6》一书中,他大概将“生成器函数”称为“生成器”,而将“生成器对象”称为“迭代器”。要点是,它们实际上都是与“生成器”相关的—一个是生成器函数,另一个是生成器对象或生成器。生成器对象同时符合可迭代协议和迭代器协议。

如果它只是一个符合迭代器协议的对象,则不能使用[...iter]for (a of iter)。它必须是符合可迭代协议的对象。

然后,在将来的JavaScript规范中,还有一个新的Iterator类仍在草稿中。它具有较大的界面,包括的方法如forEachmapreduce当前阵列接口,和新的,如和的takedrop。当前的迭代器仅通过next接口引用对象。

要回答原始问题:迭代器和可迭代的对象有什么区别,答案是:迭代器是具有interface的对象.next(),而iterable是可以提供零参数函数的对象obj,该对象obj[Symbol.iterator]在被调用时,返回一个迭代器。

生成器既是可迭代的,又是迭代器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.