javascript - 在 JavaScript 中,一个可迭代对象应该可以重复迭代吗?
问题描述
我发现有些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
并且它会返回true
.iterable.next
也是一个函数。所以在这种情况下,iterable
是一个生成器对象、可迭代对象和迭代器,所有三个)。但我想知道可迭代对象是一种对象类型,是否有明确定义的行为,是否应该或不应该重复迭代。)
解决方案
好的,我想我会在评论中总结我们学到的一些东西并添加更多内容,然后通过写下您的具体问题的答案来结束。
[...x]
句法
该语法适用于支持接口[...x]
的事物。iterables
而且,要支持可迭代接口,您所要做的就是支持该Symbol.iterator
属性以提供一个(在调用时)返回迭代器的函数。
内置迭代器也是可迭代的
Javascript 中内置的所有迭代器都派生自同一个IteratorPrototype
. 迭代器不需要这样做,这是内置迭代器做出的选择。
这个内置IteratorPrototype
也是一个Iterable。它支持Symbol.iterator
只是做的功能的属性return this
。这是按规范。
这意味着所有内置的迭代器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]
要成为 Iterator,您需要实现的只是支持该.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 对象。根据规范,该 Generator 对象的行为既是 anIterator
又是 an Iterable
。故意没有办法判断这个 Iterator/Iterable 是否来自生成器,这显然是故意的。调用代码只知道它是一个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]()
您可以通过调用which is what [...iterable]
does来要求它提供一个迭代器。但是,当你这样做时,它会返回一个全新的 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
. 它实现了 theIterable
和接口,但是当你Iterator
要求它时,它每次都返回相同的对象(本身)。因此,每次执行时,它都在同一个迭代器上运行。但是该迭代器已用尽,并且处于您第一次执行后的状态。所以,后两个是空数组。迭代器没有什么可提供的了。Iterator
[...iterable]
[...iterable]
done
[...iterable]
[...iterable]
你的问题
是否有一个规则是否应该重复迭代?
并不真地。首先,最终到达done
状态的给定迭代器(非无限迭代器)一旦到达done
状态就会给出任何结果。根据迭代器的定义。
因此,Iterable
表示某种静态序列的 an 是否可以重复迭代取决于Iterator
它在每次被询问时提供的迭代器是否是新的和唯一的,我们在上面的两个示例中已经看到,一个Iterable
可以去任何一种方式。
它每次都可以生成一个新的、唯一的迭代器,每次都通过序列呈现一个新的迭代。
或者,每次都Iterable
可以产生完全相同的结果。Iterator
如果这样做,一旦该迭代器进入该done
状态,它就会卡在那里。
还请记住,某些 Iterables 代表可能不可重复的动态集合/序列。Set
对于 a或 a之类的东西并非如此Map
,但更多自定义类型的 Iterables 可能在迭代时本质上“消耗”它们的集合,当它完成时,即使你得到一个新的新迭代器,也不会再有更多的集合了。
想象一个迭代器,它给你一个价值在 1 美元到 10 美元之间的随机金额的代码,并在你每次向迭代器询问下一个值时从你的银行余额中减去它。在某些时候,您的银行余额达到$0
并且该迭代器已完成,甚至获得一个新的迭代器仍将不得不处理相同的$0
银行余额(没有更多值)。那将是“消耗”值或某些资源并且不可重复的迭代器的示例。
但我想知道 iterable 是一种对象类型,是否有明确定义的行为,它是否应该重复迭代。
不,它是特定于实现的,完全取决于您正在迭代的内容。使用像 aSet
或 aMap
或 an这样的静态集合Array
,您可以获取一个新的迭代器并每次生成一个新的迭代。但是,我所说的 a 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");
}
推荐阅读
- python - 如何在 python 中的 Pi Zero 上设置 Google AIY 语音套件中声音文件的播放音量?
- python - 重复请求时出现 TwitterConnectionError
- java - Android Studio,java.lang.ClassCastException:java.lang.String 无法转换为 java.lang.Long
- angular - 使用 Docker 和 Nginx 服务 Angular 不起作用
- angular - 重定向到 Keycloak 时来自 Angular 的 CORS 错误
- flutter - 如何将抽屉放在 TabBar 的左侧?
- graphql - 图表:没有为子图查询中的 ID 参数提供值
- html - 如何在 CSS 中使用 flex-shrink 换行?
- r - 带有密码输入的 TextInputIcon(来自 Shinywidgets)
- react-native - 使用 useRecoilValue 时 React Native Invalid hook call