为什么 Javascript `iterator.next()` return 是一个对象?

Why does Javascript `iterator.next()` return an object?

求助!在用 C# 编程了一段时间后,我开始喜欢上 Javascript,但我一直在学习喜欢可迭代协议!

为什么 Javascript 采用需要为每次迭代创建一个新对象的 protocol?为什么 next() return 具有属性 donevalue 的新对象,而不是采用像 C# IEnumerableIEnumerator 这样不分配对象的协议以需要两次调用为代价(一次调用 moveNext 以查看迭代是否完成,第二次调用 current 以获取值)?

是否存在跳过 next() 分配对象 return 的底层优化?很难想象,因为 iterable 不知道如何使用一次对象 returned...

生成器似乎不会重用下一个对象,如下图所示:

function* generator() {
  yield 0;
  yield 1;
}

var iterator = generator();
var result0 = iterator.next();
var result1 = iterator.next();

console.log(result0.value) // 0
console.log(result1.value) // 1

嗯,here's 一个线索(感谢 Bergi!):

We will answer one important question later (in Sect. 3.2): Why can iterators (optionally) return a value after the last element? That capability is the reason for elements being wrapped. Otherwise, iterators could simply return a publicly defined sentinel (stop value) after the last element.

并且在Sect. 3.2 他们讨论使用 Using generators as lightweight threads。似乎说 return 来自 next 的对象的原因是即使 donetrue 也可以 return 编辑 value!哇。此外,除了 yieldyield* 值之外,生成器还可以 return 值,并且当 [=17] 时 return 生成的值最终与 value 相同=] 是 true

所有这些都允许伪线程。而这个特性,伪线程,值得为每次循环分配一个新对象......Javascript。总是那么出乎意料!


虽然,现在我想起来了,允许 yield* 到 "return" 一个值来启用伪线程仍然不能证明 return 一个对象是合理的。 IEnumerator 协议可以扩展到 return 在 moveNext() returns false 之后的对象——只需添加一个 属性 hasCurrent在迭代完成后测试当 true 指示 current 具有有效值时...

并且编译器优化非常重要。这将导致迭代器的性能出现相当大的差异......这不会给库实现者带来问题吗?

所有这些观点都是在 this thread 友好的 SO 社区发现的。然而,这些论点似乎并没有站得住脚。


但是,无论 return 对象与否,没有人会在迭代 "complete" 之后检查值,对吗?例如。大多数人都会认为以下内容会记录迭代器 returned 的所有值:

function logIteratorValues(iterator) {
  var next;
  while(next = iterator.next(), !next.done)
    console.log(next.value)
}

除非它不是因为即使 donefalse 迭代器 可能仍然有 returned 另一个值。 考虑:

function* generator() {
  yield 0;
  return 1;
}

var iterator = generator();
var result0 = iterator.next();
var result1 = iterator.next();

console.log(`${result0.value}, ${result0.done}`) // 0, false
console.log(`${result1.value}, ${result1.done}`) // 1, true

return在其"done"之后有一个值的迭代器真的是迭代器吗?一只手拍手的声音是什么?看起来很奇怪...


并且 here 深入 post 我喜欢的发电机。很多时间花在控制应用程序的流程上,而不是迭代集合的成员。


另一种可能的解释是 IEnumerable/IEnumerator 需要两个接口和三个方法,而 JS 社区更喜欢单一方法的简单性。这样他们就不必引入符号方法组的概念,也就是接口......

Are there under-the-hood optimizations that skip the allocation of the object return by next()?

是的。这些迭代器结果对象很小,而且通常是短暂的。特别是在 for … of 循环中,编译器可以进行简单的转义分析,以查看该对象根本不面向用户代码(而仅面向内部循环评估代码)。它们可以被垃圾收集器非常有效地处理,甚至可以直接在堆栈上分配。

以下是一些来源:

Bergi 已经回答了,我投了赞成票,我只想补充一点:

你为什么还要担心新对象被返回?看起来像:

{done: boolean, value: any}

你知道,无论如何你都会用到value,所以这真的不是额外的内存开销。还剩什么? done: boolean 和对象本身每个最多占用 8 个字节,这是可能的最小可寻址内存,必须由 cpu 处理并在几皮秒或纳秒内分配到内存中(我认为它是皮秒- 考虑到可能存在的 v8 优化)。现在,如果您仍然关心浪费 的时间和内存,那么您真的应该考虑从 JS 切换到 Rust+WebAssembly 之类的东西。