Ответ 1
Здесь есть два довольно разных вопроса:
- Есть ли или не Node.js сделать TCO?
- Как эта магическая способность работает в Node.js?
Есть ли или не Node.js сделать TCO?
TL; DR: Больше нет, начиная с Node 8.x. Это было какое-то время, за одним флагом или другим, но на момент написания этой статьи (ноябрь 2017 года) это уже не так, потому что основной движок V8 JavaScript, который он использует, больше не поддерживает TCO. Подробнее см. этот ответ.
Подробнее:
Оптимизация Tail-call (TCO) - это требуемая . Таким образом, поддержка этого не является, прямо-таки, задачей NodeJS, это то, что поддерживает V8 JavaScript-движок, который поддерживает NodeJS.
Как и в случае с Node 8.x, V8 не поддерживает TCO, даже не за флагом. Он может сделать (снова) в какой-то момент в будущем; см. этот ответ, чтобы узнать больше об этом.
Node 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 ( "функции генератора" ), поэтому снова нужно что-то, что V8 должен реализовать. Он полностью реализован в версии V8 в Node 6.6.0 (и был для нескольких версий) и не отстает от каких-либо флагов.
Функции генератора (те, что написаны с помощью function*
и с помощью yield
) работают, поскольку они могут останавливать и возвращать итератор, который фиксирует их состояние и может использоваться для продолжения своего состояния в последующем случае. У Алекса Раушмайера есть углубленная статья о них здесь.
Вот пример использования итератора, явно возвращаемого функцией генератора, но вы обычно этого не сделаете, и мы сразу увидим, почему:
"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
и avalue
. Если флаг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
для нас.