好的,我想总结一下我们在评论中学到的一些东西并添加一些内容,然后通过回答您的具体问题来结束。
[...x]
语法
[...x]
语法适用于支持 iterables
接口的对象。而要支持 iterable 接口,您只需支持 Symbol.iterator
属性以提供一个函数(调用时返回迭代器)即可。
内置迭代器也是可迭代的
Javascript 中内置的所有迭代器都源自同一个IteratorPrototype
。不要求迭代器这样做,这是内置迭代器所做的选择。
此内置的 IteratorPrototype
也是可迭代的。它支持 Symbol.iterator
属性,该属性是一个函数,只需执行 return this
即可。这是规范要求的。
这意味着所有内置迭代器,如 someSet.values()
,都可以使用 [...x]
语法。我不确定这有什么超级有用的地方,但它肯定会导致关于可迭代对象和迭代器可以做什么以及它们如何工作的混乱,因为这些内置迭代器可以表现为任一者。
这会导致一些奇怪的行为,因为如果您这样做:
let s = new Set([1,2,3]);
let iter = s.values();
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]
属性将其变成可迭代对象,它就可以像[...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]
一样工作。
生成器
当调用生成器函数时,它会返回一个生成器对象。根据规范,该生成器对象被视为迭代器和可迭代对象。故意没有办法告诉迭代器/可迭代对象是否来自生成器,这显然是有意为之。调用代码只知道它是一个迭代器/可迭代对象,生成器函数只是创建序列的一种透明方式。它像任何其他迭代器一样迭代。
两个迭代器的故事
在您的原始问题中,您展示了两个迭代器,一个可以重复工作,而另一个则不能。这里有两件事情要处理。
首先,一些迭代器“消耗”它们的序列,并且没有办法只是重复迭代同一序列。这些将是制造的序列,而不是静态集合。
其次,在您的第一个代码示例中:
const iterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 3;
yield 5;
}
}
console.log([...iterable]);
console.log([...iterable]);
console.log([...iterable]);
分离迭代器
可迭代对象是一个可迭代的集合。它不是一个迭代器。您可以通过调用iterable[Symbol.iterator]()
来请求迭代器,这就是[...iterable]
所做的事情。但是,当您这样做时,它会返回一个全新的生成器对象,即一个全新的迭代器。每次调用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]();
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)。它实现了 Iterable和Iterator接口,但是当你像[...iterable]一样请求Iterator时,它每次都会返回相同的对象(也就是自身)。因此,每次执行[...iterable]时,它都在操作同一个iterator。但是iterator已经被耗尽,并且在第一次执行[...iterable]后处于done状态。所以,接下来的两个[...iterable]返回的将是空数组。iterator已经没有更多可以给出的值了。
你的问题
是否有规则规定可迭代对象是否应该可以重复迭代?
并没有。首先,任何达到done状态的iterator(一个非无限iterator)一旦达到done状态就不再提供任何结果,这是iterator的定义。
因此,表示某种静态序列的Iterable是否可以重复迭代取决于每次请求Iterator时它提供的Iterator是否是新的和唯一的。正如上面的两个例子所示,一个Iterable可以采用不同的方式。
它可以每次产生一个新的、独特的iterator,每次都提供一个新的遍历序列的机会。
或者,一个Iterable可以每次都产生完全相同的Iterator。如果是这样,一旦iterator达到done状态,就会卡在那里。
还要注意,一些Iterable表示动态集合/序列,可能不可重复。这对于像Set或Map这样的东西并不是真的,但更定制化类型的Iterable可能会在迭代时“消耗”其集合,并且一旦完成,即使您获得一个新的新鲜Iterator,也没有更多了。
想象一下一个iterator,每次交付给你一个价值介于1美元和10美元之间的代码,并从您的银行余额中扣除相应的金额。在某个时候,您的银行余额会变为0美元,而该iterator已经完成,即使获得一个新的iterator,仍然必须处理相同的0美元银行余额(没有更多的值)。这是一个iterator"消耗"值或某些资源,无法重复遍历的例子。
但我想知道,作为对象类型的iterable是否有明确定义的行为,规定它应该还是不应该可以重复迭代。
不是的。这取决于你要迭代的内容和实现方式。对于像Set
、Map
或者Array
这样的静态集合,你可以获取一个新的迭代器,并在每次迭代时生成一个全新的迭代。但是,像我所说的"伪可迭代"(每次请求返回相同迭代器的可迭代对象)或者在迭代时已被"消耗"掉的可迭代对象可能无法重复进行迭代。因此,它可以有意地是两种情况中的任何一种。没有标准方法。这取决于正在迭代的内容。
测试你所拥有的内容
以下是几个有用的测试,可帮助您更好地理解:
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;
}
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) {
return false;
}
throw new Error("Can't tell if it's a generator object or not by design");
}
it = iterable[Symbol.iterator]
,那么你就不能做超过一次[...it]
,因为迭代器已经完成了。在第二种情况下,重复调用[...generatorFn()]
是可以的,因为你每次都会得到一个新的迭代器(和第一个版本一样)。 - VLAZ{done: true}
,就表示已经完成了。一个可迭代对象应该能够根据需要提供一个新的迭代器来开始新的迭代。理解“可迭代对象”和“迭代器”的区别非常重要。“可迭代对象”是指可以获取迭代器并使用该迭代器遍历可迭代对象中所有项的对象。 - jfriend00iterable
是一个可迭代对象,但显然它没有这样做。 - nonopolarity[...iter]
。 - nonopolarity[...iterator]
显然只适用于假装也是可迭代对象的迭代器。您可以查看iterator
接口规范,它不要求(甚至不提到)迭代器通过返回自身来假装成可迭代对象。这是内置迭代器自己决定的事情。我确信有时候这可能很方便,但它肯定会混淆可迭代对象和迭代器之间的界线(正如我们发现的那样)。 - jfriend00