# Рекурсия, стек В коде функции могут вызывать другие функции для выполнения подзадач. Частный случай подвызова -- когда функция вызывает сама себя. Это называется *рекурсией*. В этой главе мы рассмотрим, как рекурсия устроена изнутри, и как её можно использовать. [cut] ## Реализация pow(x, n) через рекурсию Чтобы возвести `x` в натуральную степень `n` -- можно умножить его на себя `n` раз в цикле: ```js function pow(x, n) { var result = x; for(var i=1; ixn = x * xn-1, т.е. можно вынести один `x` из-под степени. Иначе говоря, значение функции `pow(x,n)` получается из `pow(x, n-1)` умножением на `x`. Этот процесс можно продолжить. Например, вычислим `pow(2, 4)`: ```js pow(2, 4) = 2 * pow(2, 3) = 2 * 2 * pow(2, 2) = 2 * 2 * 2 * pow(2, 1) = 2 * 2 * 2 * 2; ``` Процесс перехода от `n` к `n-1` останавливается на `n==1`, так как очевидно, что `pow(x,1) == x`. Код для такого вычисления: ```js //+ run function pow(x, n) { if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) return x * pow(x, n-1); } else { return x; } } alert( pow(2, 3) ); // 8 ``` Говорят, что "функция `pow` *рекурсивно вызывает сама себя*" при `n != 1`. Значение, на котором рекурсия заканчивается называют *базисом рекурсии*. В примере выше базисом является `1`. Общее количество вложенных вызовов называют *глубиной рекурсии*. В случае со степенью, всего будет `n` вызовов. Максимальная глубина рекурсии ограничена и составляет около `10000`, но это число зависит от конкретного интерпретатора JavaScript и может быть в 10 раз меньше. **Рекурсию используют, когда вычисление функции можно свести к её более простому вызову, а его -- еще к более простому, и так далее, пока значение не станет очевидно.** ## Контекст выполнения, стек Теперь мы посмотрим, как работают рекурсивные вызовы. Для этого мы рассмотрим, как вообще работают функции, что происходит при вызове. **У каждого вызова функции есть свой "контекст выполнения" (execution context).** Контекст выполнения -- это служебная информация, которая соответствует текущему запуску функции. Она включает в себя локальные переменные функции. Например, для вызова: ```js //+ run function pow(x, n) { if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) return x * pow(x, n-1); } else { return x; } } *!* alert( pow(2, 3) ); // (*) */!* ``` При запуске функции `pow` в строке `(*)` будет создан контекст выполнения, который будет хранить переменные `x = 2, n = 3`. Мы схематично обозначим его так: Далее функция `pow` начинает выполняться. Вычисляется выражение `n != 1` -- оно равно `true`, ведь в текущем контексте `n=3`. Поэтому задействуется первая ветвь `if` : ```js if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) *!* return x * pow(x, n-1); */!* } else { return x; } ``` Чтобы вычислить выражение `x * pow(x, n-1)`, требуется произвести запуск `pow` с новыми аргументами. **При любом вложенном вызове JavaScript запоминает место, где он остановился в текущей функции в специальной внутренней структуре данных -- "стеке контекстов".** Это как если бы мы куда-то ехали, но очень захотелось поесть. Можно остановиться у кафе, оставить машину, отойти, а потом, через некоторое время, вернуться к ней и продолжить дорогу. Так и здесь -- мы запомним, где остановились в этой функции, пойдём выполним вложенный вызов, затем вернёмся и продолжим дорогу. **После того, как текущий контекст выполнения сохранён в стеке контекстов, JavaScript приступает к выполнению вложенного вызова.** В данном случае вызывается та же `pow`, однако, это абсолютно неважно. Для любых функций процесс одинаков. **Создаётся новый контекст выполнения, и управление переходит в подвызов, а когда он завершён -- старый контекст достаётся из стека и выполнение внешней функции возобновляется.** ## Разбор примера Разберём происходящее более подробно, начиная с вызова `(*)`: ```js //+ run function pow(x, n) { if (n != 1) { // пока n!=1 сводить вычисление pow(..n) к pow(..n-1) return x * pow(x, n-1); } else { return x; } } *!* alert( pow(2, 3) ); // (*) */!* ```
`pow(2, 3)`
Запускается функция `pow`, с аргументами `x=2`, `n=3`. Эти переменные хранятся в контексте выполнения, схематично изображённом ниже:
  • Контекст: { x: 2, n: 3 }
Выполнение в этом контексте продолжается, пока не встретит вложенный вызов в строке 3.
`pow(2, 2)`
В строке `3` происходит вложенный вызов `pow` с аргументами `x=2`, `n=2`. Для этой функции создаётся новый текущий контекст (выделен красным), а предыдущий сохраняется в "стеке":
  • Контекст: { x: 2, n: 3 }
  • Контекст: { x: 2, n: 2 }
`pow(2, 1)`
Опять вложенный вызов в строке `3`, на этот раз -- с аргументами `x=2`, `n=1`. Создаётся новый текущий контекст, предыдущий добавляется в стек:
  • Контекст: { x: 2, n: 3 }
  • Контекст: { x: 2, n: 2 }
  • Контекст: { x: 2, n: 1 }
Выход из `pow(2, 1)`.
При выполнении `pow(2, 1)`, в отличие от предыдущих запусков, выражение `n != 1` будет равно `false`, поэтому сработает вторая ветка `if..else`: ```js function pow(x, n) { if (n != 1) { return x * pow(x, n-1); } else { *!* return x; // первая степень числа равна самому числу */!* } } ``` Здесь вложенных вызовов нет, так что функция заканчивает свою работу, возвращая `2`. Текущий контекст больше не нужен и удаляется из памяти, из стека восстанавливается предыдущий:
  • Контекст: { x: 2, n: 3 }
  • Контекст: { x: 2, n: 2 }
Возобновляется обработка внешнего вызова `pow(2, 2)`.
Выход из `pow(2, 2)`.
...И теперь уже `pow(2, 2)` может закончить свою работу, вернув `4`. Восстанавливается контекст предыдущего вызова:
  • Контекст: { x: 2, n: 3 }
Возобновляется обработка внешнего вызова `pow(2, 3)`.
Выход из `pow(2, 3)`.
Самый внешний вызов заканчивает свою работу, его результат: `pow(2, 3) = 8`.
Глубина рекурсии в данном случае составила: **3**. Как видно из иллюстраций выше, глубина рекурсии равна максимальному числу контекстов, одновременно хранимых в стеке. [smart] В самом конце, как и в самом начале, выполнение попадает во внешний код, который находится вне любых функций. Контекст, который соответствует самому внешнему коду, называют *"глобальный контекст"*. Естественно, он является начальной и конечной точкой любых вложенных подвызовов. [/smart] Обратим внимание на требования к памяти. Рекурсия приводит к хранению всех данных для неоконченных внешних вызовов в стеке, в данном случае это приводит к тому, что возведение в степень `n` хранит в памяти `n` различных контекстов. Реализация степени через цикл гораздо более экономна: ```js function pow(x, n) { var result = x; for(var i=1; i .function-execution-context { margin: 0; padding: 0; overflow: auto; } .function-execution-context li { float: left; clear: both; border: 1px solid black; font-family: "PT Mono", monospace; padding: 3px 5px; } .function-execution-context li:last-child { color: red; } [/head]