Рекурсия в общем смысле - это повторение какого-либо объекта или процесса внутри самого этого объекта или процесса.
В программировании в том числе и в JavaScript под рекурсией чащего всего понимают метод, который предполагает создание функции, которая вызывает саму себя до тех пор пока программа не достигнет необходимого результата.
Рассмотрим простой пример
let days = 0;
function howMuchToLearnJS() {
days++;
console.log(days);
howMuchToLearnJS();
}
howMuchToLearnJS();
В данном примере мы написали функцию, которая:
days
;howMuchToLearnJS()
, тоесть саму себя.В результате мы получим рекурсию и в дополнение «бесконечный» вывод чисел, каждое из которых будет больше на 1-цу предыдущего. Однако есть такое понятие, как стек, который рассчитан на ограниченное количество вложенных вызовов. В итоге, где-то на 11 тысячах вывод прекратиться и console выдаст ошибку Uncaught RangeError: Maximum call stack size exceeded
- превышен максимальный размер стека вызовов.
Для того, чтобы такой ситуации не произошло в рекурсивной функции прописывают базовый случай - условие, когда выполнение программы должно закончится. Для большей надежности рекомендуют сначала прописать условие выхода, а затем описывать функционал.
let days = 0;
function howMuchToLearnJS() {
if (days === 400) return; // условие выхода
days++;
console.log(days);
howMuchToLearnJS();
}
howMuchToLearnJS();
Любая задача, которую можно решить рекурсией, также решается и с помощью цикла. Перепишим наш пример.
for (let days = 0; days <= 400; days++) {
console.log(days);
}
Тут возникает вопрос, зачем использовать рекурсию, когда можно обойтись циклом. Дело в том, что в разных ситуациях один метод можно применять с большей эффективностью, чем другой и наоборот.
Плюсы рекурсии
Плюсы цикла
Точного ответа, что лучше цикл и рекурсия нет. Все зависит от конкретной задачи.
Контекст выполнения - это структура данных, содержащая информацию о вызываемой функции. Другими словами, когда функция запускается, создается контекст, в который записывается все относящееся к вызываемой программе от переменных до того места где находился интерпретатор, когда она была вызвана.
Стек в случае с рекурсией - это специальная структура данных в которую записывается по порядку информация о каждом вызове функции самой себя, в том числе и первом вызове.
Отсюда исходит основная опасность работы с рекурсией - это переполнение стека. Глубина рекурсии равняется количеству контекстов, которые хранятся в стеке одновременно, а это число ограничено мощностью компьютера - обычно от 10 000 до 12 000. Для недопущения переполнения и существует базовый случай, тоесть условие, когда вызов функции самой себя должен прекратиться.
Разбавим теорию популярным примером и напишим функцию вычисляющую факториал.
Факториал - это произведение всех натуральных чисел от 1 до заданного включительно.
Способ №1 - рекурсия
let s = 1;
function calcFactorial(n) {
if (n === 0) return;
s = s * n;
calcFactorial(n - 1);
}
calcFactorial(10);
console.log(s);
Способ №2 - рекурсия
function calcFactorial(n) {
if (n <= 1) {
return 1;
}
return n * calcFactorial(n - 1);
}
console.log(calcFactorial(10));
Способ №3 - цикл
function calcFactorial(n) {
let s = 1;
for (let i = 1; i <= n; i++) {
s = s * i;
}
console.log(s);
}
calcFactorial(10);
Объекты с неизвестной глубиной вложенности - это классический пример, когда с помощью рекурсии задачу решить намного легче, чем с помощью цикла.
let jsUniversity = {
javascript: [{
name: 'Дмитрий',
payment: 20000,
}, {
name: 'Ольга',
payment: 20000
}, {
name: 'Вероника',
payment: 20000
}],
react: {
'full time': [{
name: 'Татьяна',
payment: 30000,
}, {
name: 'Витор',
payment: 30000,
}],
extramural: [{
name: 'Павел',
payment: 25000,
}, {
name: 'Денис',
payment: 25000,
}]
}
};
function income(devUniversity) {
if (Array.isArray(devUniversity)) {
return devUniversity.reduce((prev, current) => prev + current.payment, 0);
} else {
let sum = 0;
for (let extramural of Object.values(devUniversity)) {
sum += income(extramural);
}
return sum;
}
}
alert(income(jsUniversity));
На входе объект с несколькими уровнями вложенности - стоит задача рассчитать доход университета со всех учеников. Если глубина структуры заранее известна, найти нужные элементы с помощью цикла не так уж и сложно, но как только появится дополнительная глубина, программу придется дописывать. В нашем случае доход рассчитан с помощью рекурсии, при добавлении новых направлений или групп, код продолжил работать корректно.
Рекурсия - это метод написания функции, когда она вызывает саму себя. С помощью рекурсии можно решить много задач, но большинство из них можно легко переписать используя цикл.
Структуры данных с глубокой вложенностью или неизвестной глубиной - отличный пример, когда рекурсия справляется с обходом лучше, чем цикл.
Базовый случай - это условие, когда рекурсия должна закончится. Такое условие важно предусмотреть, так как количество вызовов функции самой себя ограничено из-за переполнения стека.