[Перевод] Функции высшего порядка в JavaScript
Одной из особенностей JavaScript, которая делает его столь удобным для функционального программирования, является то, что он может принимать функции высшего порядка. Функция высшего порядка — это функция, которая может принимать другую функцию в качестве аргумента или возвращать другую функцию в качестве результата.Функции первого класса
Вы, наверное, слышали, что JavaScript относится к функциям, как к объектам первого класса. Это выражение всего лишь означает, что в JavaScript функции имеют тот же статус, что и объекты: им присваивается тип Object; их можно задавать как значение переменной; их можно передавать и возвращать, как любые другие исходные переменные.
Эта «родная» функциональность делает JavaScript мощной силой в функциональном программировании. Так как функции являются объектами, JavaScript поддерживает естественный подход к функциональному программированию. По большому счету, этот подход так естественен, что вы наверняка и не задумывались об этом.
Принимаем функции как аргументы
Если вы всерьез занимались разработкой на JavaScript в Сети или веб-разработкой в целом, то наверняка встречались с функциями, которые используют обратный вызов. Обратный вызов — функция, которая выполняется в конце операции, когда все остальные операции уже выполнены. Как правило, функция обратного вызова передается в качестве последнего аргумента функции, но нередко бывает и так, что ее задают в строке в качестве анонимной функции.
Так как JavaScript является однопотоковым языком программирования, то есть операции выполняются поочередно, каждая операция выстраивается в очередь на поступление в единый поток. Стратегия передачи функции на выполнение, когда остальные родительские функции завершены, является одной из основных характеристик языков программирования, поддерживающих функции высшего порядка. Так, можно получить асинхронное поведение, то есть скрипт, ожидая результат, все же продолжит выполняться. Возможность передачи обратного вызова критична при работе с ресурсами, которые могут возвратить результат через неопределенный промежуток времени.
Такой подход очень удобен в среде веб-разработки, когда скрипт может отослать Ajax запрос на сервер, а затем обработать ответ вне зависимости от времени его получения, а также без учета сетевых задержек и времени обработки на сервере. Node.js часто обращается к обратным вызовам для наиболее эффективного использования серверных мощностей. Данный подход также эффективен в случае с приложением, которое ожидает ввода данных от пользователя до начала выполнения функции.
Например, обратите внимание на сниппет простого JavaScript кода ниже, который добавляет слушателя событий к кнопке:
button id="clicker">So Clickable
document.getElementById("clicker").addEventListener("click", function() {
alert("you triggered " + this.id);
});
Данный скрипт использует анонимную строковую функцию для отображения предупреждения. Однако, он мог с тем же успехом использовать отдельно заданную функцию и передавать эту названную функцию методу addEventListener:
var proveIt = function() {
alert("you triggered " + this.id);
};
document.getElementById("clicker").addEventListener("click", proveIt);
Обратите внимание, что мы передали proveIt, а не proveIt () нашей функции addEventListener. Когда Вы передаете функцию по имени без круглых скобок, вы передаете функцию-объект. Когда же вы передаете ее с круглыми скобками, то передается результат выполнения функции.
Функция proveIt () структурно независима от кода вокруг нее и, следовательно, будет всегда возвращать id любого запущенного элемента. Фактически, данная часть кода может существовать в любом контексте, в котором вам необходимо отобразить предупреждение с id элемента. Кроме того, его можно вызвать с помощью любого слушателя событий.
Замена строковой функции любой отдельно заданной и названной функцией дает много возможностей. По мере того, как мы пытаемся создать чистые функции, которые не меняют внешние данные и каждый раз возвращают одни и те же данные при одном и том же вводе, мы получаем важный инструмент, который поможет нам создать библиотеку небольших, целевых функций, пригодных для использования в любом приложении.
Возвращаем функции как результаты
Кроме возможности принимать функции как аргументы, JavaScript позволяет функциям возвращать другие функции как результат. Это логично, так как функции имеют тот же статус, что и другие объекты данных, и могут быть возвращены, как и любое другое значение.
Однако, как это происходит? Если задать функцию как обратное значение другой функции, то можно создать функции, которые могут быть использованы в качестве шаблонов для создания новых функций. Это открывает дверь в функциональное царство магии на JavaScript.
Например, предположим, что Вам настолько надоело читать бесконечное количество статей о поколении 80–90-х годов, которое часто называют поколением миллениалов (millenials), что вы решаете заменять слово «millenials» на фразу «snake people» каждый раз, когда оно встречается в Сети. Обычно вы бы просто написали функцию, которая по вашему желанию заменяла бы один текст на другой:
var snakify = function(text) {
return text.replace(/millenials/ig, "Snake People");
};
console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.
Это будет работать, но только в данной конкретной ситуации. Далее предположим, что вам надоело и послевоенное поколение или бэби-бумеры (baby boomers). Вы хотите написать кастомную функцию и для них. Проблема только в том, что даже для такой простой функции вы не хотите повторять уже написанный код:
var hippify = function(text) {
return text.replace(/baby boomers/ig, "Aging Hippies");
};
console.log(hippify("The Baby Boomers just look the other way."));
// The Aging Hippies just look the other way.
Однако, что если вы решите сделать что-то более изысканное, чтобы сохранить алгоритм в коде? Для этого вам придется изменить и первую, и вторую функции. Это не просто рутинно, а еще и делает код нестабильным и сложным для восприятия.
На самом деле вам нужна гибкость в коде, возможность заменять одну переменную на другую в шаблоне функции и затем задавать поведение в качестве основной функции, из которой можно будет извлечь множество других функций.
Используя возможность возвращать функции вместо значений, JavaScript позволяет выполнить поставленную задачу гораздо эффективнее:
var attitude = function(original, replacement, source) {
return function(source) {
return source.replace(original, replacement);
};
};
var snakify = attitude(/millenials/ig, "Snake People");
var hippify = attitude(/baby boomers/ig, "Aging Hippies");
console.log(snakify("The Millenials are always up to something."));
// The Snake People are always up to something.
console.log(hippify("The Baby Boomers just look the other way."));
// The Aging Hippies just look the other way.
Выше мы свели код, на который ложится основная работа, к гибкой и расширяемой функции attitude. В ней происходят все операции, необходимые для модификации любых данных с помощью первой и второй фразы, снабженных комментариями.
Сославшись новой функцией на функцию attitude, которая уже содержит первые два аргумента, мы позволяем новой функции принимать любой передаваемый аргумент и использовать его в качестве источника текста во внутренней функции, которую возвращает функция attitude.
Так, мы используем возможность функций JavaScript не зависеть от изначально заданного количества аргументов и принимать любое их количество. Если аргумент отсутствует, то функция просто будет рассматривать его как незаданный
С другой стороны, этот дополнительный аргумент можно передать позже, когда запрашиваемую функцию задают так, как мы описали выше, а именно: в качестве ссылки на функцию, возвращенную от другой функции с одним и большим количеством незаданных аргументов.
Пройдитесь по тексту еще раз, если вы не поняли, как работают функции высшего порядка. Мы создаем шаблон функции, которая возвращает другую функцию. Затем, мы задаем эту только что возвращенную функцию, исключив один атрибут, как кастомную имплементацию шаблона функции. Все созданные этим образом функции наследуют один и тот же код из шаблона функции, однако могут по умолчанию получить различные аргументы.
Вы уже применяете функции высшего порядка
Функции высшего порядка лежат в самой основе JavaScript, так что вы их уже используете. Каждый раз, когда вам надо передать анонимную функцию или обратный вызов, вы работаете со значением, которое возвращает передаваемая функция, и используете его в качестве аргумента для другой функции.
Способность функций возвращать другие функции делает JavaScript чрезвычайно удобным и позволяет нам создавать кастомные функции для выполнения специфических задач с повсеместно используемым шаблоном функции. Каждая из этих маленьких функций получает все улучшения, которые появляются в коде шаблона функции. Это позволяет избежать дублей кода, а также сделать код более чистым и читабельным.
В качестве дополнительного бонуса: если вы убедитесь, что у функций нет побочного эффекта, так что они не изменяют значения, а всегда возвращают их неизменными для любого ввода данных, у вас появляется отличная возможность создавать наборы тестов и проверять, что изменения в коде функции шаблона не приводят к ошибкам.
Просто подумайте, как вы сможете использовать такой подход в собственных проектах. Преимущество JavaScript в том, что вы можете добавлять новые функциональности в уже используемый код. Попробуйте поэкспериментировать. вы удивитесь, как пара простейших манипуляций с функциями высшего порядка могут улучшить ваш код.
Об авторе
М. Дэвид Грин (M. David Green)
Я работал веб-разработчиком, писателем, менеджером по коммуникациям и директором отдела маркетинга в таких компаниях, как Apple, Salon.com, StumbleUpon и Moovweb. Мое исследование «Социология в телекоммуникации», которое я провожу в Калифорнийском университете в Беркли, а также степень магистра по бизнесу по теме «Организационное поведение», стали достаточным основанием понять, что человек движим инстинктом общения с себе подобными, что этот инстинкт достаточно силен и работает всегда и везде, вне зависимости от среды общения.
Над переводом работали: greebn9k (Сергей Грибняк), silmarilion (Андрей Хахарев)
Singree