在JavaScript中,可迭代项是否应重复可迭代?

问题描述 投票:6回答:3

我发现一些可迭代的对象可以重复地迭代:

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并返回trueiterable.next也是一个函数,因此,在这种情况下,iterable是一个生成器对象,一个可迭代的对象和一个迭代器,所有这三个对象),但我想知道iterable是一个对象类型,是否存在定义明确的行为是否应该重复迭代。)

javascript ecmascript-6 iterable
3个回答
2
投票

[好,我想我已经总结了我们在评论中学到的一些知识,并添加了一些内容,然后通过为您的特定问题写答案来结束。

[...x]语法

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

内置迭代器也是可迭代的

内置到Javascript中的所有迭代器均源自相同的IteratorPrototype。不需要迭代器执行此操作,这是内置迭代器做出的选择。

此内置IteratorPrototype也是可迭代的。它支持IteratorPrototype属性,该属性仅执行Symbol.iterator。这是return this

这意味着所有内置的迭代器,例如by specification,都将使用someSet.values()语法。我不确定为什么这是超级有用的,但是肯定会导致人们对Iterable可以做什么以及Iterator可以做什么感到困惑,因为这些内置的迭代器可以两者兼有。

它会导致一些时髦的行为,因为如果执行此操作:

[...x]

第二个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)) 属性使其成为Iterable,它将作为[Symbol.iterator]

[...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);,因为它现在也是可迭代的。

Generators

生成器函数在被调用时返回一个生成器对象。对于[...iter],该Generator对象既充当spec,又充当Iterator。故意没有办法判断此Iterator / Iterable是否来自生成器,这显然是Iterable。调用代码只知道它是done on purpose,并且generator函数只是创建对调用代码透明的序列的一种方法。就像其他迭代器一样进行迭代。


您的两个迭代器的故事

在您最初的问题中,您显示两个迭代器,一个迭代器可以重复工作,而另一个则不能。这里有两件事在起作用。

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

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

Iterator/Iterable

单独的迭代器

那个可迭代就是可迭代的。它不是迭代器。您可以通过调用const iterable = { [Symbol.iterator]: function* () { yield 1; yield 3; yield 5; } } console.log([...iterable]); console.log([...iterable]); console.log([...iterable]); 来要求它提供迭代器。但是,当您这样做时,它将返回一个全新的Generator对象,它是一个全新的迭代器。每次调用iterable[Symbol.iterator]()或使用[...iterable]调用它时,都会得到一个新的不同的迭代器。

您可以在这里看到:

iterable[Symbol.iterator]()

因此,您正在为每个迭代器创建一个全新的序列。它新鲜地调用了generator函数来获取一个新的generator对象。

相同的迭代器

但是,使用第二个示例:

[...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]); 的东西。它同时实现了iterablepseudo-iterable接口,但是当您像Iterable一样要求它提供Iterator时,它每次都会(本身)返回相同的对象。因此,每次执行Iterator时,它都在同一迭代器上运行。但是,在您第一次执行[...iterable]之后,该迭代器已经耗尽,并处于[...iterable]状态。因此,后两个done是空数组。迭代器别无其他。

您的问题

是否有一个可重复的规则,是否应该重复可重复?

不是。首先,完成最终到达[...iterable]状态的给定迭代器(非无限迭代器),一旦到达[...iterable]状态就给出任何结果。根据迭代器的定义。

因此,表示某种静态序列的done是否可以重复迭代取决于每次请求迭代器时提供的done是新的且唯一的,并且从以上两个示例可以看出,Iterable可以任意选择。

每次都可以产生一个新的,唯一的迭代器,每次都在序列中提供新的迭代。

或者,Iterator每次都可以产生完全相同的Iterable。如果那样做,则该迭代器进入Iterable状态后,就会卡在其中。

还请记住,某些Iterables表示动态的集合/序列,这些序列/序列可能不可重复。对于Iteratordone之类的情况并非如此,但是更多的自定义类型的Iterable可能本质上在迭代和完成后“消耗”其集合,即使您得到了新的新鲜迭代器。

想象一下,一个迭代器向您提供了一个介于$ 1到$ 10之间的随机值的代码,并在每次您请求迭代器提供下一个值时从您的银行余额中减去该值。在某个时候,您的银行余额达到Set,并且迭代器已经完成,即使使用新的迭代器,仍然必须处理相同的Map银行余额(没有更多值)。那将是一个迭代器的例子“消耗”值或某些资源,并且是不可重复的。

但是我想知道可迭代作为对象类型,是否存在可重复迭代的明确定义的行为。

没有它是特定于实现的,并且完全取决于您要迭代的内容。使用$0$0Set之类的静态集合,您可以每次获取新的迭代器并生成新的迭代。但是,我所谓的Map(每次请求都返回相同迭代器的可迭代对象)或在迭代时序列被“消耗”的可迭代对象可能无法重复迭代。因此,可以有目的地选择任何一种方式。没有标准方法。这取决于要迭代的内容。

测试您拥有的东西

这里有一些有用的测试可以帮助您稍微了解一些内容:

Array

1
投票

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"); } 也是可迭代的,也是iterable的对象。

[const iterable = generatorFn();对象由Generator返回,它同时与GeneratorGenerator一致。

此生成器跟随generator function并仅以可迭代方式运行一次。


0
投票

iterable protocol有一个很好的实现,似乎表明我们希望并且可迭代地被重新迭代:

iterator protocol

-1
投票

是否有一个可重复的规则是否应该重复可重复?

没有当然,有一些可迭代的用例,只能用一次。

© www.soinside.com 2019 - 2024. All rights reserved.