【问题标题】:In JavaScript ES6, what is the difference between an iterable and iterator?在 JavaScript ES6 中,可迭代对象和迭代器有什么区别?
【发布时间】:2020-04-14 21:58:39
【问题描述】:

iterable 和 iterator 是一样的还是不同的?

看来,from the specifications,一个可迭代对象是一个对象,比如说,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)

所以在上面的代码中,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

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

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

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

【问题讨论】:

标签: javascript ecmascript-6 iterator iterable


【解决方案1】:

是的,iterablesiterators 是不同的东西,但大多数迭代器(包括您从 JavaScript 本身获得的所有迭代器,例如来自 keys 或 @ Array.prototype 上的 987654323@ 方法或来自生成器函数的生成器)继承自 %IteratorPrototype% object,它有一个 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);
}

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

function range(start, end) {
    let value = start;
    let inc = start < end ? 1 : -1;
    return {
        next() {
            const done = value == end;
            const result = {done, value};
            if (!done) {
                value += inc;
            }
            return result;
        }
    };
}

// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
    console.log(result.value);
}

// Fails when an iterable is expected
try {
    for (const value of range(1, 5)) {
        console.log(value);
    }
} catch (e) {
    console.error(e.message);
}

range 返回的迭代器不是可迭代的,所以当我们尝试将它与for-of 一起使用时它会失败。

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

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

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

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

function* range(start, end) {
    let value = start;
    let inc = start < end ? 1 : -1;
    while (value !== end) {
        yield value;
        value += inc;
    }
}

// Works when used directly
const it = range(1, 5);
let result;
while (!(result = it.next()).done) {
    console.log(result.value);
}

// Also works when an iterable is expected
for (const value of range(1, 5)) {
    console.log(value);
}

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

【讨论】:

    【解决方案2】:

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

    根据the ES6 SpecsMDN

    当我们有

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

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

    let bar = foo();
    

    bar 是一个生成器对象。还有a generator object conforms to both the iterable protocol and the iterator protocol

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

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

    通过title of the MDN link,似乎我们也可以将生成器对象称为“生成器”。

    请注意,在Nicolas Zakas's book Understanding ECMAScript 6 中,他可能松散地将“生成器函数”称为“生成器”,将“生成器对象”称为“迭代器”。要点是,它们实际上都与“生成器”相关——一个是生成器函数,一个是生成器对象或生成器。生成器对象同时符合可迭代协议和迭代器协议。

    如果它只是一个符合 iterator 协议的对象,您不能使用[...iter]for (a of iter)。它必须是一个符合 iterable 协议的对象。

    然后,还有a new Iterator class, in a future JavaScript specs that is still in a draft。它有一个更大的接口,包括当前Array接口的forEachmapreduce等方法,以及takedrop等新的方法。当前迭代器引用的对象只有next 接口。

    回答最初的问题:迭代器和可迭代对象有什么区别,答案是:迭代器是具有接口.next()的对象,而可迭代对象是对象obj使得obj[Symbol.iterator]可以给出一个零参数函数,该函数在调用时返回一个迭代器。

    生成器既是可迭代的,又是迭代器,添加到它。

    【讨论】:

      【解决方案3】:

      这是最简单的区别:

      • iterator - 任何具有 next 函数以返回下一个值的对象
      • iterable - 任何具有[Symbol.iterator] 函数并返回iterator 的对象

      但是当你有一个同时做这两个的对象时,它被称为IterableIterator。例如,任何generator 函数都返回一个。

      IterableIterator 的典型实现如下:

      {
          [Symbol.iterator]() {
              return this; // returning iterator
          },
          next() {
              // return next value here
          }
      } //=> IterableIterator
      

      【讨论】:

        猜你喜欢
        • 2014-12-05
        • 1970-01-01
        • 2020-09-20
        • 2012-02-10
        • 2010-11-04
        • 2018-08-16
        • 2018-01-28
        • 2020-06-29
        • 2012-06-14
        相关资源
        最近更新 更多