这里有两个比较明显的问题:
- Node.js是否支持TCO?
- 在Node.js中,这个神奇的yield是如何工作的?
Node.js是否支持TCO?
TL;DR: 自 Node 8.x 起不再支持。在某些标志位后面,Node.js 曾经支持 TC0。但截至本文(2017年11月),因为底层的 V8 JavaScript 引擎不再支持 TCO,Node.js 不再支持它。请参见此答案了解更多信息。
详情:
尾调用优化(TCO)是 ES2015(“ES6”)规范的必要部分。因此,支持它并不是一个 Node.js 的特性,而是需要 Node.js 使用的 V8 JavaScript 引擎支持它。
从 Node 8.x 开始,V8 不再支持 TCO,即使是在标志位后面也不行。以后可能会再次支持它;请参见此答案了解更多信息。
Node 7.10 到至少 6.5.0(我的笔记说是 6.2,但node.green的数据不同)只在严格模式下支持 TCO(在早期使用 --harmony_tailcalls
,在 6.6.0 及更高版本中使用 --harmony
标志位)。
如果您想检查您的安装情况,请参见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());
$ # 仅适用于某些版本的Node,特别是不适用于8.x或(当前)9.x;请参见上文
$ node --harmony tco.js
true
true
Node.js中这个神奇的yield
怎么起作用?
这又是一个ES2015的东西("generator functions"),所以它是V8必须实现的。在Node 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);
}
代码的输出结果如下:
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
属性,这些内容通常会干扰我们所需完成的实际操作。因此,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
检查。
node --harmony mytest.js
。但首先重新查看您引用的示例,您只适应了其中的一部分到您的情况。关于TCO,真正的问题是V8是否已经实现了它 - 我在v8 changelog中没有看到任何提及。 - barry-johnson