这里有两个相当不同的问题:
- Node.js 有没有 TCO?
- 这个神奇的 yield 东西在 Node.js 中是如何工作的?
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());
$ # 仅限某些版本的 Node,特别是 8.x 或(当前)9.x;往上看
$节点--harmony tco.js
真的
真的
yield 这个神奇的东西在 Node.js 中是如何工作的?
这是另一个 ES2015 的东西(“生成器函数”),所以这也是 V8 必须实现的东西。它在 Node 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 属性都是(通常)妨碍我们尝试做的所有样板,所以 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 为我们检查。