JS: Функциональное программирование
Теория: Выполнение функций
Вспомним несколько важных понятий из прошлого курса:
Выражение — код, выполнение которого возвращает значение. Инструкция — код, представляющий собой команду.
- Выражения — вычисляются.
- Инструкции — исполняются.
К выражениям относятся:
- Вызов функции
- Арифметические и логические операции
- Тернарный оператор
- и другие
К инструкциям относятся:
forwhilebreakreturnif- и другие
Список выражений и инструкций на этом не заканчивается, с полным перечнем можно ознакомиться в документации (см. дополнительные материалы к уроку).
Для лучшего понимания, как могут взаимодействовать друг с другом различные конструкции языка, необходимо знать разницу между ними.
Возьмем условный оператор if. В некоторых языках он представлен инструкцией, в некоторых — выражением. Посмотрите на код ниже и подумайте, возможно ли такое в JavaScript и почему:
Такой код в JavaScript невозможен только по одной простой причине: if — это инструкция, а не выражение.
Но такой код возможен и часто используется в языках вроде Ruby или Python. Зачем же нужен if, если есть тернарный оператор, который как раз и является выражением?
Дело вот в чем. Если бы if был выражением, то тернарный оператор стал бы попросту не нужен, несмотря на то, что он является более лаконичной заменой if. Но и тернарный оператор бывает неудобен в тех ситуациях, когда вычисление слишком большое и не помещается в одну строку, а его результат должен быть записан в одну и ту же константу или переменную в каждой из веток. В такой ситуации как раз пригодился бы if как выражение.
Этот пример ярко иллюстрирует тот факт, что конструкции языка, представленные выражением, делают язык гибче, а решения с их использованием делают код лаконичнее. Другими словами, язык становится выразительнее. Кроме описанного выше, у выражений есть еще одно огромное преимущество: они могут комбинироваться друг с другом и вкладываться друг в друга.
Ниже рассмотрим варианты комбинирования выражений, но с акцентом на функции:
Арифметические операции
На последней строке может возникнуть вопрос: насколько допустимо складывать результаты функций? Вызов функции — это выражение, возвращающее результат, так что этот код допустим на 100%. Если функция вернет значение, неподходящее для сложения, то может возникнуть ошибка, но это будет логическая ошибка, а не синтаксическая.
Примечание: предполагается, что используемые в примерах этого урока функции и константы (например, f2 или isEditing) ранее где-то были определены. Их определение мы убрали из примеров, чтобы не отвлекать от главного.
Логические операции
Все то же самое можно делать и с логическими выражениями:
Из-за слабой типизации подобный код работать будет вообще всегда, даже если функции возвращают не true или false, но пользоваться этим не стоит. (Типизация рассматривалась в уроке предыдущего курса).
Аргументы
А теперь чуть более сложный пример. Когда мы вызываем функцию, то в аргументах ожидается выражение: func(<expression>, <expression>, ...). А из этого следует, что мы можем сделать так:
Если последний вызов вам кажется сложным, то вспомните вот что. Когда мы изучали русский язык в школе, то постоянно делали грамматический разбор предложения, в котором выделяли существительное, подлежащее, сказуемое, различные обороты и многое другое. Здесь нужно сделать то же самое. Очень важно уметь разбивать в уме сложное выражение на составные.
Пройдемся по примеру выше. f(f1(f2(n3, f3(n1, n2))), f4()) содержит в аргументах два выражения:
f4()f1(f2(n3, f3(n1, n2)))— этот вызов содержит один аргумент — вызов функции:f2(n3, f3(n1, n2)), - который в свою очередь содержит два аргумента:n3и вызов функцииf3(n1, n2).
Порядок выполнения
Осталось понять, в какой последовательности происходят эти вызовы. JavaScript считается энергичным языком, то есть языком с аппликативным порядком вычисления, а это значит, что аргументы вычисляются до того, как попадают внутрь функций.
Как видно, вычисление идет с самого глубокого уровня слева направо. Каждая функция попадает в стек вызовов, где работает принцип "первым вошел - последним вышел". Поскольку каждый аргумент - тоже функция, то они также добавляются за ней следом. Те функции, которые возвращают результат, сразу вычисляются. Так получается, что стек наполняется и очищается до тех пор, пока не выполнится f1.
