【问题标题】:In JavaScript, should an iterable be repeatedly iterable?在 JavaScript 中,一个可迭代对象应该可以重复迭代吗?
【发布时间】:2020-04-23 00:25:19
【问题描述】:

我发现有些iterable可以重复iterable:

const iterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 3;
    yield 5;
  }
}

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

虽然有些不能:

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

const iterable = generatorFn();

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

是否有一个可迭代对象是否应该重复迭代的规则?

我理解为什么它们的行为不同(这是因为第二种情况,当调用iterable[Symbol.iterator] 函数时,返回相同的迭代器(即iterable 本身。可以尝试iterable[Symbol.iterator]() === iterable,它会返回@987654327 @.iterable.next 也是一个函数。所以在这种情况下,iterable 是一个生成器对象、一个可迭代对象和一个迭代器,所有这三个)。但我想知道可迭代对象是一个对象类型,是否有明确定义的行为至于它是否应该重复迭代。)

【问题讨论】:

  • 不是真的,但两者也不是真的等价的。第一个是你强制迭代的对象,第二个你使用 iterator 并在它耗尽后尝试迭代。基本上,迭代器将表示它是否不再包含任何值并且之后您不能继续。因此,在第一种情况下,如果您已经完成了it = iterable[Symbol.iterator],您将无法在迭代器完成后多次执行[...it]。在第二种情况下,重复调用[...generatorFn()] 很好,因为您每次都采用一个新的迭代器(与第一个版本相同)。
  • 一个迭代器应该迭代直到它完成然后被完成。一旦它报告{done: true},它就完成了。一个可迭代对象应该能够根据需要提供一个新的迭代器来开始一个新的迭代。了解iterableiterator 之间的区别很重要。可迭代对象是您可以获得迭代器并可以使用该迭代器遍历可迭代对象中的所有项目的东西。
  • @jfriend00 所以你的意思是迭代应该每次都返回一个新的迭代器(理想情况下?)。在第二种情况下,iterable 是可迭代的,但显然它没有这样做
  • @jfriend00 所以只是因为在第二种情况下,iterable 来自生成器函数,那么这个 iterable 不是一个 iterable 而是不同类型的 iterable?我只知道一个对象是否符合可迭代协议,那么它正式是可迭代的。没有“是的,它是可迭代的,但它是一种不同类型的可迭代”
  • 所以,[...iterator] 显然只适用于伪装成可迭代的迭代器。您可以看到 iterator interface spec 不需要(甚至不提及)通过返回自身来伪装成可迭代的迭代器。这是内置迭代器决定自己做的事情。我敢肯定它有时会很方便,但它确实混淆了可迭代和迭代器之间的界限(正如我们所发现的那样)。

标签: javascript ecmascript-6 iterable


【解决方案1】:

const iterable = generatorFn();iterable 是一个可迭代对象,也是一个Generator 对象。

Generator 对象由generator function 返回,它符合iterable protocoliterator protocol

此生成器遵循protocol 并仅与可迭代对象一起运行一次。

【讨论】:

  • Mozilla 区分生成器函数和生成器对象(或只是生成器)。我认为,生成器也是可迭代的,否则 [...iterable] 将无法工作。如果它像鸭子一样嘎嘎叫,那它就是鸭子。 (或者至少它是可嘎嘎的)
  • 所以,这并不正确。 generatorFn() 确实返回了一个 Generator 对象。但是 Generator 对象既是 Iterable 又是 Iterator。它实现了这两个接口。您会看到 here in the specification 表达的意思,它表示 Generator 对象是生成器函数的一个实例,并且符合 Iterator 和 Iterable 接口。。故意这样做,你甚至不能说它是一个 Generator 对象。您可以检测到它既是 Iterable 又是 Iterator。
【解决方案2】:

好的,我想我会总结一下我们在 cmets 中学到的一些东西,并添加更多内容,然后写下您具体问题的答案。

[...x] 语法

[...x] 语法适用于支持iterables 接口的事物。而且,要支持可迭代接口,您所要做的就是支持 Symbol.iterator 属性以提供一个(在调用时)返回迭代器的函数。

内置迭代器也是可迭代的

Javascript 中内置的所有迭代器都派生自同一个 IteratorPrototype。迭代器不需要这样做,这是内置迭代器做出的选择。

这个内置的IteratorPrototype 也是一个Iterable。它支持Symbol.iterator 属性,这是一个只执行return this 的函数。这是by specification

这意味着所有内置迭代器(例如 someSet.values())都将使用 [...x] 语法。我不确定为什么这非常有用,但它肯定会导致混淆 Iterable 可以做什么以及 Iterator 可以做什么,因为这些内置的迭代器可以起到任何作用。

这会导致一些奇怪的行为,因为如果你这样做:

let s = new Set([1,2,3]);
let iter = s.values();    // gets an iterator
let x = [...iter];
let y = [...iter];
console.log(x);
console.log(y);

第二个[...iter] 是一个空数组,因为这里只有一个迭代器。事实上,x === y。因此第一个let x = [...iter]; 耗尽了迭代器。它位于done 上,无法再次迭代集合。这是因为内置迭代器的这种时髦行为,它们表现为可迭代的,但只是return this。他们不会创建一个新的迭代器,它可以像您使用实际的可迭代集合时那样再次迭代集合。每次访问s[Symbol.iterator]() 时,这个集合迭代器都会返回一个全新的迭代器,如下所示:

let s = new Set([1,2,3]);
let x = [...s];
let y = [...s];
console.log(x);
console.log(y);

普通迭代器不适用于[...x]

要成为一个迭代器,您需要实现的只是支持.next() 方法并使用适当的对象进行响应。其实这里有一个超级简单的迭代器,符合规范:

const iter = { 
    i: 1, 
    next: function() { 
        if (this.i <= 3) {
            return { value: this.i++, done: false }; 
        } else {
            return { value: undefined, done: true }; 
        } 
    }
}

如果你尝试做let x = [...iter];,它会抛出这个错误:

TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

但是,如果您通过向其添加适当的 [Symbol.iterator] 属性使其成为 Iterable,它将作为 [...iter] 工作;

const iter = { 
    i: 1, 
    next: function() { 
        if (this.i <= 3) {
            return { value: this.i++, done: false }; 
        } else {
            return { value: undefined, done: true }; 
        } 
    },
    [Symbol.iterator]: function() { return this; }
}

let x = [...iter];
console.log(x);

然后,它可以作为[...iter] 工作,因为它现在也是一个可迭代对象。

发电机

Generator 函数在被调用时会返回一个 Generator 对象。根据spec,该生成器对象的行为既是Iterator,又是Iterable。故意无法判断此 Iterator/Iterable 是否来自生成器,这显然是 done on purpose。调用代码只知道它是Iterator/Iterable,而生成器函数只是创建对调用代码透明的序列的一种方法。它像任何其他迭代器一样被迭代。


两个迭代器的故事

在您最初的问题中,您展示了两个迭代器,一个重复工作,一个不重复工作。这里有两件事在起作用。

首先,一些迭代器“消耗”了它们的序列,并且没有办法重复迭代相同的序列。这些将是制造的序列,而不是静态集合。

其次,在您的第一个代码示例中:

const iterable = {
  [Symbol.iterator]: function* () {
    yield 1;
    yield 3;
    yield 5;
  }
}

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

单独的迭代器

那个iterable是一个iterable。它不是一个迭代器。你可以通过调用iterable[Symbol.iterator]() 来请求它提供一个迭代器,这就是[...iterable] 所做的。但是,当你这样做时,它会返回一个全新的 Generator 对象,它是一个全新的迭代器。每次调用 iterable[Symbol.iterator]() 或使用 [...iterable] 调用时,都会得到一个新的不同的迭代器。

你可以在这里看到:

    const iterable = {
      [Symbol.iterator]: function* () {
        yield 1;
        yield 3;
        yield 5;
      }
    }

    let iterA = iterable[Symbol.iterator]();
    let iterB = iterable[Symbol.iterator]();
    
    // shows false, separate iterators on separate generator objects
    console.log(iterA === iterB);      

因此,您正在为每个迭代器创建一个全新的序列。它重新调用生成器函数以获取新的生成器对象。

相同的迭代器

但是,用你的第二个例子:

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

const iterable = generatorFn();

console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);

不一样。你在这里称呼iterable 是我喜欢的pseudo-iterable。它同时实现了IterableIterator 接口,但是当您像[...iterable] 那样要求它提供Iterator 时,它每次(本身)都返回相同的对象。因此,每次您执行[...iterable] 时,它都在同一个迭代器上运行。但是在您第一次执行[...iterable] 之后,该迭代器已用尽并且处于done 状态。所以,后两个[...iterable] 是空数组。迭代器没有什么可提供的了。

您的问题

是否有一个可迭代对象是否应该重复迭代的规则?

不是真的。首先,最终到达done 状态的给定迭代器(非无限迭代器)一旦到达done 状态就会给出任何结果。根据迭代器的定义。

因此,代表某种静态序列的Iterable 是否可以重复迭代取决于它在每次被询问迭代器时提供的Iterator 是否是新的和唯一的,并且我们'在上面的两个示例中已经看到,Iterable 可以采用任何一种方式。

它每次都可以产生一个新的、唯一的迭代器,每次都通过序列呈现一个新的迭代。

或者,Iterable 每次都可以生成完全相同的Iterator。如果这样做,一旦该迭代器进入done 状态,它就会卡在那里。

还请记住,某些 Iterables 表示可能不可重复的动态集合/序列。对于 SetMap 之类的东西,情况并非如此,但是更多自定义类型的 Iterables 可能在迭代时基本上“消耗”他们的集合,当它完成时,即使你得到一个新的新鲜迭代器。

想象一个迭代器,它给你一个价值在 1 美元到 10 美元之间的随机金额的代码,并在你每次向迭代器询问下一个值时从你的银行余额中减去它。在某些时候,您的银行余额达到$0 并且该迭代器已完成,即使获得一个新的迭代器仍然必须处理相同的$0 银行余额(没有更多值)。这将是一个迭代器的例子 “消耗”值或某些资源,只是不可重复。

但我想知道 iterable 作为一种对象类型,是否有明确定义的行为是否应该或不应该重复迭代。

没有。它是特定于实现的,完全取决于您正在迭代的内容。使用像 SetMapArray 这样的静态集合,您可以获取一个新的迭代器并每次生成一个新的迭代。但是,我称之为psuedo-iterable(每次请求时返回相同迭代器的迭代)或迭代时序列被“消耗”的迭代可能无法重复迭代。所以,它可以故意是任何一种方式。没有标准的方法。这取决于正在迭代的内容。

测试你所拥有的

这里有一些有用的测试可以帮助人们理解一些事情:

// could do a more comprehensive test by calling `obj.next()` to see if
// it returns an appropriate object with appropriate properties, but
// that is destructive to the iterator (consumes that value) 
// so we keep this one non-destructive
function isLikeAnIterator(obj) {
    return typeof obj === "object" && typeof obj.next === "function)";
}

function isIterable(obj) {
    if (typeof obj === "object" && typeof obj[Symbol.iterator] === "function") {
        let iter = obj[Symbol.iterator]();
        return isLikeAnIterator(iter);
    }
    return false;
}

// A pseudo-iterable returns the same iterator each time
// Sometimes, the pseudo-iterable returns itself as the iterator too
function isPseudoIterable(obj) {
   if (isIterable(obj) {
       let iterA = obj[Symbol.iterator]();
       if (iterA === this) {
          return true;
       }
       let iterB = obj[Symbol.iterator]();
       return iterA === iterB;
   }
   return false;
}

function isGeneratorObject(obj) {
    if (!isIterable(obj) !! !isLikeAnIterator(obj) {
        // does not meet the requirements of a generator object
        // which must be both an iterable and an iterator
        return false;
    }
    throw new Error("Can't tell if it's a generator object or not by design");
}

【讨论】:

    【解决方案3】:

    2021 年更新

    MDN doc 已更改以反映耗尽的迭代器不应使其自身再次可迭代。他们的示例现在到达done: true 并且不会重置this.index = 0。这与jfriend00's answer得出的结论是一致的。

    此更正早于 MDN 迁移到 github,因此我没有更改历史记录。我将在下面留下之前的答案。


    这个答案可能是错误的(见上面的更新)

    MDN doc 有一个很好的实现,这似乎表明我们更喜欢和 iterable 是可重复的:

    [Symbol.iterator]() {
      return {
        next: () => {
          if (this.index < this.data.length) {
            return {value: this.data[this.index++], done: false};
          } else {
            this.index = 0; //If we would like to iterate over this again without forcing manual update of the index
            return {done: true};
          }
        }
      };
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-12
      • 2019-06-03
      • 2014-04-16
      • 2013-07-13
      • 1970-01-01
      • 2019-12-03
      • 1970-01-01
      相关资源
      最近更新 更多