到目前为止,我喜欢JavaScript,并决定使用Node.js作为我的引擎,部分原因是this,声称Node.js提供了TCO。但是,当我尝试使用Node.js运行此代码(显然是尾部调用)时,会导致堆栈溢出:
function foo(x) {
if (x == 1) {
return 1;
}
else {
return foo(x-1);
}
}
foo(100000);
现在,我做了一些挖掘,然后找到了this。在这里,似乎我应该这样写:
function* foo(x) {
if (x == 1) {
return 1;
}
else {
yield foo(x-1);
}
}
foo(100000);
然而,这给了我语法错误。我尝试了各种各样的排列,但在所有情况下,Node.js似乎对某事不满意。
基本上,我想知道以下内容:
yield
如何在Node.js中起作用?答案 0 :(得分:38)
这里有两个截然不同的问题:
Node.js是否做TCO?
TL; DR :不再是,从Node 8.x 开始。它有一段时间,在一面旗帜或其他旗帜之后,但在撰写本文时(2017年11月)它已经不存在了,因为它使用的基础V8 JavaScript引擎不再支持TCO。有关详情,请参阅this answer。
详细说明:
尾部呼叫优化(TCO)是必需的part of the ES2015 ("ES6") specification。因此,支持它不是一个NodeJS,它是NodeJS使用的V8 JavaScript引擎需要支持的东西。
从Node 8.x开始,V8不支持TCO,甚至不支持标志。它可能(再次)在未来的某个时刻;有关详情,请参阅this answer。
至少节点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());
$ # 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的另一个东西(&#34;生成器功能&#34;),所以它也是V8必须实现的东西。它完全在节点6.6.0中的V8版本中实现(并且已经有多个版本)并且不在任何标志之后。
生成器函数(使用function*
并使用yield
编写的函数)通过能够停止并返回捕获其状态的迭代器来工作,并可用于在后续场合继续其状态。 Alex Rauschmeyer有一篇关于他们的深度文章here。
这是一个明确使用生成器函数返回的迭代器的示例,但您通常不会这样做,我们会在一瞬间看到原因:
"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);
}
有这个输出:
0 1 2 3 4
这是如何运作的:
counter
(let it = counter(0, 5);
)时,初始化对counter
的调用的初始内部状态,我们立即返回迭代器; counter
中没有任何实际代码运行(尚未)。it.next()
会在counter
中运行第一个yield
语句中的代码。此时,counter
暂停并存储其内部状态。 it.next()
返回一个状态对象,其中包含done
标记和value
。如果done
标记为false
,则value
是yield
语句产生的值。it.next()
都会将counter
内的状态提升到下一个yield
。it.next()
的调用使counter
完成并返回时,我们返回的状态对象将done
设置为true
并将value
设置为返回值counter
。拥有迭代器和状态对象的变量,并调用it.next()
并访问done
和value
属性,这些都是(通常)妨碍我们和#39;重新尝试这样做,所以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);
}
v
与我们上一个示例中的state.value
相对应,for-of
为我们执行了所有it.next()
次呼叫和done
次检查。
答案 1 :(得分:4)
node.js最终支持自2016.05.17起的TCO,version 6.2.0。
需要执行--use-strict --harmony-tailcalls
标志才能使TCO正常工作。
答案 2 :(得分:0)
6.2.0 - 使用'use strict'和'--harmony_tailcalls'
仅适用于小尾部优化的10000递归(如问题中所示),但函数调用本身失败99999999999999次。
即使使用99999999999999,7.2.0使用'use strict'和'--harmony'
flag也能无缝快速地工作。
答案 3 :(得分:-1)
更简洁的答案......截至发布之日,如上所述......
TCO有效。它不是防弹的,但它非常体面。这是因子(7000000,1)。
>node --harmony-tailcalls -e "'use strict';function f(n,t){return n===1?t:f(n-1,n*t);}; console.log(f(7000000,1))"
Infinity
这里没有TCO。
>node -e "'use strict';function f(n,t){return n===1?t:f(n-1,n*t);}; console.log(f(15669,1))"
[eval]:1
function f(n,t){return n===1?t:f(n-1,n*t);}; console.log(f(15669,1))
^
RangeError: Maximum call stack size exceeded
at f ([eval]:1:11)
at f ([eval]:1:32)
at f ([eval]:1:32)
at ...
它确实让它一直到15668。
关于产量,请参阅其他答案。应该是一个单独的问题。