为什么 Javascript `iterator.next()` 返回一个对象? [英] Why does Javascript `iterator.next()` return an object?
问题描述
帮助!在用 C# 编程一段时间后,我开始学习喜欢 Javascript,但我一直在学习喜欢可迭代协议!
为什么 Javascript 采用协议 需要为每次迭代创建一个新对象?为什么 next()
返回一个具有 done
和 value
属性的新对象,而不是采用像 C# IEnumerable
这样的协议和 IEnumerator
不分配任何对象,代价是需要两次调用(一次到 moveNext
以查看迭代是否完成,第二次到 current
> 获取值)?
是否存在跳过 next()
返回的对象分配的底层优化?很难想象,因为可迭代对象不知道返回后如何使用对象...
生成器似乎不会重用下一个对象,如下图所示:
function* generator() {产量 0;产量 1;}var iterator = generator();var result0 = iterator.next();var result1 = iterator.next();console.log(result0.value)//0console.log(result1.value)//1
嗯,这里是一个线索(感谢 Bergi!):
<块引用>我们稍后会回答一个重要问题(在第 3.2 节):为什么迭代器(可选)可以在最后一个元素之后返回一个值?这种能力是元素被包装的原因.否则,迭代器可以简单地在最后一个元素之后返回一个公开定义的标记(停止值).
在教派中.3.2 他们讨论使用使用生成器作为轻量级线程.似乎说从 next
返回一个对象的原因是为了即使 done
为 true> 也可以返回一个
value
代码>!哇.此外,除了 yield
和 yield*
-ing 值和由 return
生成的值之外,生成器还可以 return
值当 done
为 true
时,以 value
结束!
所有这些都允许伪线程.而这个特性,伪线程,值得为每次循环分配一个新对象......Javascript.总是那么出乎意料!
<小时>虽然,现在我考虑了一下,允许 yield*
返回"一个值以启用伪线程仍然不能证明返回一个对象是合理的.IEnumerator
协议可以扩展为在 moveNext()
返回 false
后返回一个对象——只需添加一个属性 hasCurrent
> 在迭代完成后测试当 true
表示 current
具有有效值时...
而且编译器优化非常重要.这将导致迭代器的性能出现相当大的差异......这不会给库实现者带来问题吗?
所有这些观点都在这个主题 由友好的 SO 社区发现.然而,这些争论似乎站不住脚.
<小时>然而,无论是否返回一个对象,在迭代完成"之后,没有人会检查一个值,对吧?例如.大多数人都会认为以下内容会记录迭代器返回的所有值:
function logIteratorValues(iterator) {下一个变量;while(next = iterator.next(), !next.done)控制台日志(下一个值)}
除非它没有,因为即使 done
是 false
,迭代器仍然可能返回另一个值. 考虑:
function* generator() {产量 0;返回 1;}var iterator = generator();var result0 = iterator.next();var result1 = iterator.next();console.log(`${result0.value}, ${result0.done}`)//0, falseconsole.log(`${result1.value}, ${result1.done}`)//1, true
完成"后返回值的迭代器真的是迭代器吗?一只手拍手的声音是什么?只是看起来很奇怪...
<小时>这里是关于我喜欢的生成器的深入帖子.与迭代集合的成员相比,很多时间都花在了控制应用程序的流程上.
<小时>另一种可能的解释是 IEnumerable/IEnumerator 需要两个接口和三个方法,而 JS 社区更喜欢单一方法的简单性.这样他们就不必引入符号方法组又名接口的概念......
是否存在跳过
next()
返回的对象分配的底层优化?
是的.那些迭代器结果对象很小并且通常是短暂的.特别是在 for ... of
循环中,编译器可以做一个简单的转义分析来查看对象根本不面对用户代码(而只面对内部循环评估代码).它们可以由垃圾收集器非常有效地处理,甚至可以直接在堆栈上分配.
以下是一些来源:
- JS 从 Python 继承了它的功能性迭代协议,但是 带有结果对象而不是以前喜欢的
StopIteration
例外 - 规范讨论中的性能问题 (续) 被忽略了.如果您实现自定义迭代器并且速度太慢,请尝试使用生成器函数
- (至少对于内置迭代器)这些优化已经实施一>:<块引用>
优秀迭代性能的关键是确保循环中对
iterator.next()
的重复调用得到很好的优化,理想情况下完全避免分配iterResult
使用高级编译器技术,如存储加载传播、逃逸分析和聚合的标量替换.为了真正提高性能,优化编译器还应该完全消除iterator
本身的分配 -iterable[Symbol.iterator]()
调用 - 并对直接后备可迭代对象.
Help! I'm learning to love Javascript after programming in C# for quite a while but I'm stuck learning to love the iterable protocol!
Why did Javascript adopt a protocol that requires creating a new object for each iteration? Why have next()
return a new object with properties done
and value
instead of adopting a protocol like C# IEnumerable
and IEnumerator
which allocates no object at the expense of requiring two calls (one to moveNext
to see if the iteration is done, and a second to current
to get the value)?
Are there under-the-hood optimizations that skip the allocation of the object return by next()
? Hard to imagine given the iterable doesn't know how the object could be used once returned...
Generators don't seem to reuse the next object as illustrated below:
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
Hm, here's a clue (thanks to 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.
And in Sect. 3.2 they discuss using Using generators as lightweight threads. Seems to say the reason for return an object from next
is so that a value
can be returned even when done
is true
! Whoa. Furthermore, generators can return
values in addition to yield
and yield*
-ing values and a value generated by return
ends up as in value
when done
is true
!
And all this allows for pseudo-threading. And that feature, pseudo-threading, is worth allocating a new object for each time around the loop... Javascript. Always so unexpected!
Although, now that I think about it, allowing yield*
to "return" a value to enable a pseudo-threading still doesn't justify returning an object. The IEnumerator
protocol could be extended to return an object after moveNext()
returns false
-- just add a property hasCurrent
to test after the iteration is complete that when true
indicates current
has a valid value...
And the compiler optimizations are non-trivial. This will result in quite wild variance in the performance of an iterator... doesn't that cause problems for library implementors?
All these points are raised in this thread discovered by the friendly SO community. Yet, those arguments didn't seem to hold the day.
However, regardless of returning an object or not, no one is going to be checking for a value after iteration is "complete", right? E.g. most everyone would think the following would log all values returned by an iterator:
function logIteratorValues(iterator) {
var next;
while(next = iterator.next(), !next.done)
console.log(next.value)
}
Except it doesn't because even though done
is false
the iterator might still have returned another value. Consider:
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
Is an iterator that returns a value after its "done" is really an iterator? What is the sound of one hand clapping? It just seems quite odd...
And here is in depth post on generators I enjoyed. Much time is spent controlling the flow of an application as opposed to iterating members of a collection.
Another possible explanation is that IEnumerable/IEnumerator requires two interfaces and three methods and the JS community preferred the simplicity of a single method. That way they wouldn't have to introduce the notion of groups of symbolic methods aka interfaces...
Are there under-the-hood optimizations that skip the allocation of the object return by
next()
?
Yes. Those iterator result objects are small and usually short-lived. Particularly in for … of
loops, the compiler can do a trivial escape analysis to see that the object doesn't face the user code at all (but only the internal loop evaluation code). They can be dealt with very efficiently by the garbage collector, or even be allocated directly on the stack.
Here are some sources:
- JS inherits it functionally-minded iteration protocol from Python, but with results objects instead of the previously favoured
StopIteration
exceptions - Performance concerns in the spec discussion (cont'd) were shrugged off. If you implement a custom iterator and it is too slow, try using a generator function
- (At least for builtin iterators) these optimisations are already implemented:
The key to great performance for iteration is to make sure that the repeated calls to
iterator.next()
in the loop are optimized well, and ideally completely avoid the allocation of theiterResult
using advanced compiler techniques like store-load propagation, escape analysis and scalar replacement of aggregates. To really shine performance-wise, the optimizing compiler should also completely eliminate the allocation of theiterator
itself - theiterable[Symbol.iterator]()
call - and operate on the backing-store of the iterable directly.
这篇关于为什么 Javascript `iterator.next()` 返回一个对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!