Рекурсия
Рекурсия
Рекурсия — функция, которая вызывает саму себя. Мощный инструмент для задач, которые можно разбить на меньшие подзадачи той же структуры.
Структура рекурсивной функции
Каждая рекурсивная функция должна иметь:
- Базовый случай — условие завершения рекурсии (без него — бесконечный стек)
- Рекурсивный случай — вызов себя с уменьшенным аргументом
function countdown(n) {
// Базовый случай — остановиться при 0
if (n <= 0) {
console.log('Готово!');
return;
}
console.log(n);
countdown(n - 1); // Рекурсивный вызов с уменьшенным n
}
countdown(3);
// 3
// 2
// 1
// Готово!
Классический пример: факториал
// Рекурсивный факториал
function factorial(n) {
if (n <= 1) return 1; // Базовый случай
return n * factorial(n - 1); // Рекурсивный случай
}
factorial(5); // 5 * 4 * 3 * 2 * 1 = 120
// Стек вызовов:
// factorial(5) = 5 * factorial(4)
// = 5 * 4 * factorial(3)
// = 5 * 4 * 3 * factorial(2)
// = 5 * 4 * 3 * 2 * factorial(1)
// = 5 * 4 * 3 * 2 * 1
// = 120
Когда рекурсия уместна
Рекурсия хороша для:
- Древовидных структур (обход DOM, файловой системы)
- Алгоритмов “разделяй и властвуй” (quicksort, merge sort)
- Задач, где решение выражается через меньшее подрешение
// Обход вложенных комментариев
function renderComments(comments) {
return comments.map(comment => ({
text: comment.text,
replies: comment.replies.length
? renderComments(comment.replies) // Рекурсия для вложенных
: []
}));
}
JavaScript имеет ограниченный размер стека вызовов (обычно ~10 000 — 15 000 кадров). Глубокая рекурсия вызовет
RangeError: Maximum call stack size exceeded.Stack Overflow и его предотвращение
// Опасно — стек может переполниться для больших n
function sum(n) {
if (n <= 0) return 0;
return n + sum(n - 1);
}
sum(100000); // RangeError!
// Итеративная версия — безопасна
function sumIterative(n) {
let result = 0;
for (let i = 1; i <= n; i++) result += i;
return result;
}
Хвостовой вызов — рекурсивный вызов как последнее действие функции. Теоретически движок может оптимизировать такой вызов, не добавляя новый кадр стека:
// НЕ хвостовая — результат factorial(n-1) используется в умножении
function factorial(n) {
return n * factorial(n - 1);
}
// Хвостовая — аккумулятор передаётся как аргумент
function factorialTCO(n, acc = 1) {
if (n <= 1) return acc;
return factorialTCO(n - 1, n * acc); // последнее действие — вызов
}
V8 поддерживает TCO только в строгом режиме и не всегда. На практике для больших данных лучше использовать итерацию.