Потому что каждая ступенька из описанного рекурсивного вызова вычисляема. Ошибка выкинится когда следущий шаг вычисления будет невозможен по той или иной причине. Например, ошибка появится когда ни один вызов функции не вычисляется из-за того что они все вызывают рекурсивно себя. Тогда у JS-а закончится возможность запоминания того какая функция ждлет результат вычисления какой функции (эта система запоминания называется call stack или стек вызова).
Пока бер рекурсии посмотрим на выражение 10 + getNum()
. Чтобы его вычислить js находит функцию getNum
и вызывает ее, выполняет все выражения и нее до самого return
(явного или неявного), и, получив выражение подставляет его в выражение 10 + getNum()
.
А что если getNum
внутри себя вызывает другую функцию, допустим getType
? Тогда интерпретатор, вычисляя значение getNum
, дойдет до строки вызова getType
, и начинает вычислять ее значение проходя по всем выражениям внутри getType
. A это время функция getNum
как бы ждет вычисления getType
, потому что результат вычисления нужен getNum
для возврата значения вычисления себя. А что если
getTypeвызывает еще другую функцию допустим
getEnv? Тогда история повторяется,
getTypeбудет ждать результата вызова
getEnv. В это время
getNumпродолжает "ждать"
getType`.
Именно это ожидание, зависимость от результатов вложенных вызовов и визуально описано в лесенке которую я давал в предыдущем ответе.
Возвращаемся к рекурсии.
power(2, 1)
2 * power(2, 0)
1
js пытается вычислить power(2, 1)
. Выполняя тело функции power
, ему для этого нужно вычислить 2 * power(2, 0)
, где на место второго множителя подставляется результат вызова power(2, 0)
. Результат вызова power(2,0)
вычисляется в 1, подставляется в 2 * power(2,0)
и получает результат вызова power(2, 1)
.
Ну и ИИ:
Ваш вопрос касается того, как JavaScript обрабатывает вызовы функций и почему в данном случае не возникает ошибка при попытке умножения числа на результат функции, которая еще не завершила свое выполнение. Ответ на этот вопрос связан с тем, как работает механизм вызова функций и рекурсии в JavaScript.
Когда в JavaScript выполняется вызов функции, выполнение кода внутри этой функции начинается и продолжается до тех пор, пока функция не достигнет оператора return или не достигнет конца своего тела. Если функция вызывает саму себя (рекурсивный вызов), новый вызов функции также начинает выполняться, создавая новый контекст выполнения для этого вызова. Этот процесс продолжается, создавая цепочку вызовов, каждый из которых ожидает завершения следующего вызова в цепочке, чтобы продолжить выполнение.
В контексте вашего примера, когда выполняется строка return base * power(base, exponent -1), JavaScript не пытается немедленно умножить base на результат функции power. Вместо этого, он сначала выполняет вызов power(base, exponent -1), ожидая его завершения. Каждый вызов power продолжает выполняться, пока не достигнет базового случая, который возвращает числовое значение (в данном случае 1), а не вызов функции. Только после того, как рекурсивный вызов возвращает число, JavaScript производит умножение и возвращает результат в предыдущий вызов функции.
Это означает, что в любой момент времени JavaScript точно знает, что делать с каждым выражением и операцией. Он не пытается умножить число на функцию; он умножает число на результат вызова функции, который является числом. Это ключевое различие, которое позволяет избежать ошибок, связанных с попыткой выполнить недопустимые операции, такие как умножение числа на функцию.
Таким образом, благодаря четко определенной последовательности вызовов функций и возвратов из них, JavaScript может корректно обрабатывать рекурсивные вызовы и выполнять соответствующие операции без возникновения ошибок, связанных с типами данных или недопустимыми операциями.