Найти в Дзене
Frontend Fusion Life!

Хвостовая рекурсия в JavaScript!

Оглавление

Что такое хвостовая рекурсия?

Хвостовая рекурсия — это форма рекурсии, при которой рекурсивный вызов функции происходит в самом конце функции и является последним действием перед возвратом результата. Это важное свойство позволяет компилятору или интерпретатору оптимизировать выполнение рекурсивных вызовов и снизить потребление памяти.

Преимущества хвостовой рекурсии

  1. Оптимизация памяти: В традиционной рекурсии каждый вызов функции добавляет новый фрейм в стек вызовов. Это может привести к переполнению стека, особенно при глубокой рекурсии. В хвостовой рекурсии, благодаря оптимизации хвостовых вызовов (Tail Call Optimization, TCO), фреймы не накапливаются. В результате, память используется более эффективно.
  2. Повышенная производительность: TCO позволяет преобразовать рекурсивные вызовы в циклы, что значительно ускоряет выполнение программы.
  3. Упрощение кода: Хвостовая рекурсия позволяет писать более лаконичный и читаемый код, сохраняя при этом производительность и экономию памяти.

Примеры использования рекурсии

Обычная рекурсия

Рассмотрим пример вычисления максимального элемента в массиве с использованием обычной рекурсии:

function findMax(arr) {
if (arr.length === 1) {
return arr[0];
}
// обычный рекурсивный вызов не в конце функции
const maxOfRest = findMax(arr.slice(1));
return arr[0] > maxOfRest ? arr[0] : maxOfRest;
}

В этом примере функция findMax принимает массив arr. Если длина массива равна 1, функция возвращает единственный элемент массива. В противном случае, функция вызывает саму себя с оставшейся частью массива (без первого элемента) и сохраняет результат в maxOfRest. Затем функция сравнивает первый элемент массива с maxOfRest и возвращает большее из двух значений. Рекурсивный вызов findMax(arr.slice(1)) не является последним действием в функции, поэтому этот пример не является хвостовой рекурсией. Как следствие хуже читается и воспринимается, а так же при компиляции кода не проходит оптимизации.

Хвостовая рекурсия

Теперь рассмотрим тот же пример с использованием хвостовой рекурсии:

function findMax(arr, currentMax = -Infinity) {
if (arr.length === 0) {
return currentMax;
}
const newMax = arr[0] > currentMax ? arr[0] : currentMax;
return findMax(arr.slice(1), newMax); // хвостовой рекурсивный вызов
}

В этом примере функция findMax принимает два параметра: arr и currentMax. Если массив пуст, функция возвращает currentMax, который содержит максимальное значение, найденное до сих пор. В противном случае, функция вычисляет новое максимальное значение newMax, сравнивая первый элемент массива с currentMax, и затем вызывает саму себя с оставшейся частью массива и обновленным newMax. Этот вызов является последним действием в функции, что делает его хвостовой рекурсией.

Понятно, что пример максимально синтетический, но он здесь чисто для улучшения вашего понимания.

Оптимизация в JavaScript

Теперь поговорим о поддержке данной оптимизации JavaScript-движками и рассмотрим, насколько вообще целесообразно её использование, или заморачиваться не стоит.

Tail Call Optimization, TCO — это оптимизация, при которой компилятор или интерпретатор может преобразовать рекурсивные вызовы, выполненные в хвостовой позиции (то есть последней операцией в функции), в циклы, тем самым предотвращая переполнение стека вызовов.

JavaScript, начиная с ECMAScript 2015 (ES6), поддерживает оптимизацию хвостовых вызовов как написано в спецификации. Однако, стоит отметить, что не все движки JavaScript поддерживают TCO на практике.

Движки JavaScript, поддерживающие TCO:

JavaScriptCore (JSC):

  • Это движок, используемый в браузере Safari и других продуктах Apple.
  • Поддержка TCO добавлена в версии Safari 10.
  • Работает благодаря спецификации ECMAScript 2015 (ES6), которая формально требует поддержки TCO для движков JavaScript.

Chakra:

  • Движок, использовавшийся в Microsoft Edge до перехода на Chromium.
  • Поддержка TCO была добавлена, чтобы соответствовать спецификации ES6.

Движки JavaScript, не поддерживающие TCO:

V8:

  • Используется в Google Chrome и Node.js.
  • Несмотря на поддержку спецификации ES6, V8 не реализует TCO из-за соображений производительности и сложности реализации в текущей архитектуре движка.

SpiderMonkey:

  • Движок Mozilla, используемый в Firefox.
  • Как и в случае с V8, SpiderMonkey не реализует TCO, хотя это и требование спецификации ES6.

Причины, почему некоторые движки не поддерживают TCO:

  1. Архитектурные сложности: Реализация TCO может требовать значительных изменений в архитектуре движка, что может повлиять на производительность и стабильность.
  2. Производительность: Некоторые оптимизации, используемые в современных движках, могут конфликтовать с TCO.
    TCO может усложнить другие оптимизации, такие как
    JIT-компиляция (Just-In-Time).
  3. Приоритеты разработки: Разработчики движков могут решить сосредоточиться на других улучшениях производительности и функциональности, которые считают более важными.

Заключение

Хвостовая рекурсия — это мощная техника оптимизации рекурсивных функций, позволяющая улучшить производительность и снизить потребление памяти. Но не смотря на то, что в языке JavaScript поддержка хвостовой рекурсии была введена в стандарте ES6, не все движки JavaScript в полной мере реализуют эту оптимизацию. По этому окончательное решение использовать или нет остается за вами, я лишь скажу, что от ее использования мы ничего не теряем и бонусом получаем более читаемый код, так что почему бы и нет. 😉