好的,我想我应该总结一下我们在评论中学到的一些东西,并添加更多内容,然后通过写下您的具体问题的答案来结束。
[...x]
syntax
The [...x]
语法适用于支持的事物iterables
界面。并且,要支持可迭代接口,您所要做的就是支持Symbol.iterator
属性来提供一个函数(当调用时)返回一个迭代器。
内置迭代器也是可迭代的
Javascript 中内置的所有迭代器都派生自相同的迭代器IteratorPrototype https://www.ecma-international.org/ecma-262/10.0/index.html#sec-%25iteratorprototype%25-object。不需要迭代器执行此操作,这是内置迭代器所做的选择。
这个内置IteratorPrototype
也是一个 Iterable。它支持Symbol.iterator
属性是一个函数,它的作用就是return this
。这是按规格 https://www.ecma-international.org/ecma-262/10.0/index.html#sec-%25iteratorprototype%25-@@iterator.
这意味着所有内置迭代器,例如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]
它的属性,它将作为[...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 https://www.ecma-international.org/ecma-262/10.0/index.html#sec-generator-objects,该 Generator 对象的行为既是Iterator
and an Iterable
。故意没有办法判断这个 Iterator/Iterable 是否来自生成器,这显然是故意做的 https://stackoverflow.com/a/19660350/816620。调用代码只知道它是一个Iterator/Iterable
生成器函数只是创建对调用代码透明的序列的一种方法。它的迭代就像任何其他迭代器一样。
两个迭代器的故事
在您原来的问题中,您显示了两个迭代器,一个可以重复工作,另一个则不能。这里有两件事在起作用。
首先,一些迭代器“消耗”它们的序列,并且没有办法重复迭代相同的序列。这些将是制造序列,而不是静态集合。
其次,在您的第一个代码示例中:
const iterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 3;
yield 5;
}
}
console.log([...iterable]);
console.log([...iterable]);
console.log([...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
。它实现了Iterable
和Iterator
接口,但是当你要求它提供一个Iterator
like [...iterable]
确实如此,它每次都返回相同的对象(本身)。所以,每次你这样做[...iterable]
,它在同一个迭代器上运行。但是该迭代器已耗尽并且位于done
第一次执行后的状态[...iterable]
。所以,后两个[...iterable]
是空数组。迭代器没有什么可提供的了。
你的问题
是否存在一个可迭代对象应该或不应该重复可迭代的规则?
并不真地。首先,给定的迭代器最终到达done
状态(非无限迭代器)一旦到达就完成给出任何结果done
状态。根据迭代器的定义。
所以,无论是否Iterable
表示某种静态序列可以重复迭代取决于是否Iterator
当询问迭代器时它提供的内容每次被询问时都是新的且唯一的,我们在上面的两个示例中看到,Iterable
可以选择任何一种方式。
它每次都可以生成一个新的、唯一的迭代器,每次都通过序列呈现新的迭代。
Or, an Iterable
可以产生完全相同的Iterator
每一次。如果这样做的话,一旦该迭代器到达done
状态,它被卡在那里。
还要记住,某些 Iterables 表示可能不可重复的动态集合/序列。对于像这样的事情来说,情况并非如此Set
or a Map
,但是更多自定义类型的 Iterables 可能在迭代时本质上“消耗”它们的集合,并且当它完成时,即使您获得一个新的迭代器,也不会再有更多的集合了。
想象一下,一个迭代器向您提供一个价值 1 美元到 10 美元之间的随机金额的代码,并在您每次向迭代器询问下一个值时从您的银行余额中减去该代码。在某个时候,您的银行余额会达到$0
并且该迭代器已经完成,即使获得一个新的迭代器仍然必须处理相同的问题$0
银行余额(没有更多值)。这将是一个迭代器的例子
“消耗”值或某些资源并且不可重复。
但我想知道可迭代作为一种对象类型,是否有一个明确定义的行为来确定它是否应该重复可迭代。
不。它是特定于实现的,完全取决于您要迭代的内容。使用像这样的静态集合Set
or a Map
or an Array
,您可以获取一个新的迭代器并每次生成一个新的迭代。但是,我所说的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");
}