Koz*_*oss 23 javascript tail-call-optimization node.js
我喜欢的JavaScript到目前为止,并决定使用Node.js的为我的发动机的部分原因是因为这个,它声称的Node.js提供TCO.但是,当我尝试使用Node.js运行此代码(显然是尾部调用)时,会导致堆栈溢出:
function foo(x) {
if (x == 1) {
return 1;
}
else {
return foo(x-1);
}
}
foo(100000);
Run Code Online (Sandbox Code Playgroud)
现在,我做了一些挖掘,我找到了这个.在这里,似乎我应该这样写:
function* foo(x) {
if (x == 1) {
return 1;
}
else {
yield foo(x-1);
}
}
foo(100000);
Run Code Online (Sandbox Code Playgroud)
但是,这给了我语法错误.我试过它的各种排列,但在所有的情况下,Node.js的似乎不满的东西.
基本上,我想知道以下内容:
yield东西在Node.js中如何运作?T.J*_*der 40
这里有两个截然不同的问题:
Node.js是否执行TCO?
TL; DR:现在不一样了,因为节点8.x中的.它有一段时间,在一面或另一面旗帜后面,但截至本文(2017年11月),它已不复存在,因为它使用的基础V8 JavaScript引擎不再支持TCO.有关详细信息,请参阅此答案.
细节:
尾部呼叫优化(TCO)是ES2015("ES6")规范的必要部分.所以支持它不是直接的NodeJS,它是NodeJS使用的V8 JavaScript引擎需要支持的东西.
从Node 8.x开始,V8不支持TCO,甚至不支持标志.它可能(再次)在未来的某个时刻; 有关更多信息,请参阅此答案.
至少节点7.10降至6.5.0(我的注释说6.2,但node.green不同意)仅在严格模式下支持TCO后面的标志(--harmony在6.6.0及更高--harmony_tailcalls版本中).
如果你想检查你的安装,这里是node.green使用的测试(如果你使用的是相关版本,请务必使用该标志):
function direct() {
"use strict";
return (function f(n){
if (n <= 0) {
return "foo";
}
return f(n - 1);
}(1e6)) === "foo";
}
function mutual() {
"use strict";
function f(n){
if (n <= 0) {
return "foo";
}
return g(n - 1);
}
function g(n){
if (n <= 0) {
return "bar";
}
return f(n - 1);
}
return f(1e6) === "foo" && f(1e6+1) === "bar";
}
console.log(direct());
console.log(mutual());
Run Code Online (Sandbox Code Playgroud)
$ # Only certain versions of Node, notably not 8.x or (currently) 9.x; see above $ node --harmony tco.js true true
这个神奇的
yield东西在Node.js中如何运作?
这是ES2015的另一个东西("生成器功能"),所以V8必须实现这一点.它完全在节点6.6.0中的V8版本中实现(并且已经有多个版本)并且不在任何标志之后.
生成器函数(使用function*和使用的函数yield)通过能够停止并返回捕获其状态的迭代器来工作,并可用于在随后的场合继续其状态.Alex Rauschmeyer在这里有一篇关于他们的深入文章.
这是一个显式使用生成器函数返回的迭代器的示例,但是您通常不会这样做,我们马上就会看到原因:
"use strict";
function* counter(from, to) {
let n = from;
do {
yield n;
}
while (++n < to);
}
let it = counter(0, 5);
for (let state = it.next(); !state.done; state = it.next()) {
console.log(state.value);
}
Run Code Online (Sandbox Code Playgroud)
这有输出:
0 1 2 3 4
这是如何工作的:
counter(let it = counter(0, 5);)时,counter初始化调用的初始内部状态,我们立即返回迭代器; 没有counter运行中的实际代码(还).it.next()运行代码.此时,暂停并存储其内部状态.返回带有标志和a 的状态对象.如果该标志是,则该语句产生的值.counteryieldcounterit.next()donevaluedonefalsevalueyieldit.next()将状态推进counter到下一个状态yield.it.next()品牌counter完成并返回时,状态对象,我们取回已done设置true和value设置的返回值counter.拥有迭代器和状态对象的变量,并调用it.next()和访问done和value属性都是(通常)妨碍我们尝试做的事情的所有样板,因此ES2015提供了新的for-of声明,将它全部收起来我们只是给了我们每个价值.这是上面写的相同代码for-of:
"use strict";
function* counter(from, to) {
let n = from;
do {
yield n;
}
while (++n < to);
}
for (let v of counter(0, 5)) {
console.log(v);
}
Run Code Online (Sandbox Code Playgroud)
v对应state.value于我们之前的示例,为我们for-of执行所有it.next()调用和done检查.