Поддерживают ли связанные функции правильные хвостовые вызовы в ES6?
В спецификации языка ECMAScript 2015, определения Function.prototype.apply
а также Function.prototype.call
оба включают "Perform PrepareForTailCall()" в качестве одного из своих шагов, поэтому мы знаем, что эти функции поддерживают надлежащие вызовы хвоста (т. е. оптимизация хвостового вызова).
Однако определение [[Call]] для объектов связанных функций опускает PrepareForTailCall(). Означает ли это, что связанные функции не поддерживают надлежащие хвостовые вызовы и что связанная функция, вызывающая себя рекурсивно, может взорвать стек?
2 ответа
Определение
[[Call]]
на связанных объектах функции опускаетсяPrepareForTailCall()
, Означает ли это, что связанные функции не поддерживают надлежащие хвостовые вызовы и что связанная функция, вызывающая себя рекурсивно, может взорвать стек?
№ PrepareForTailCall
происходит в EvaluateDirectCall
во время оценки выражения вызова, где он проверяет, находится ли это выражение в хвостовой позиции. Когда хвостовой вызов подготовлен, текущий контекст выполнения сбрасывается перед вызовом функции, отправляя соответствующий [[Call]]
внутренний метод. Новый контекст выполнения выполняется в PrepareForOrdinaryCall
от [[Call]]
метод пользовательских функций. [[Call]]
метод связанных функций просто вводит дополнительный уровень косвенности, прежде чем это произойдет.
В спецификации языка ECMAScript 2015, определения
Function.prototype.apply
а такжеFunction.prototype.call
оба включают " ВыполнитьPrepareForTailCall()
"как один из их шагов, поэтому мы знаем, что эти функции поддерживают правильные хвостовые вызовы.
Да, это необходимо, потому что [[Call]]
Метод встроенных функций устанавливает новый контекст выполнения (для "шагов, определенных реализацией"). Именно этот "встроенный контекст" PrepareForTailCall
упадет до того, как будет вызвана фактическая функция.
call
а также apply
методы - это функции, вызывающие функции, и когда они вызываются, в стеке есть два вызова, которые должны быть оптимизированы с помощью хвостового вызова. (Сравните это, например, с Array.prototype.map
, который также вызывает другие функции, но здесь map
контекст выполнения остается в стеке вызовов. В call
а также apply
, Call()
действительно в хвостовой позиции алгоритма).
См. §12.3.4.1 ( Семантика времени выполнения: оценка). Во-первых, мы делаем IsInTailPosition
тогда мы делаем EvaluateDirectCall
- результат которого F.[[Call]]
, Другими словами, к тому времени, когда мы делаем реальный вызов, мы уже знаем, находимся ли мы в хвостовой позиции.
Однако, как вы упомянули, JS не оптимизирован хвостом ни в одном браузере, кроме Safari.