GrabDuck

Функции высшего порядка в JavaScript

:

Одной из особенностей JavaScript, которая делает его столь удобным для функционального программирования, является то, что он может принимать функции высшего порядка. Функция высшего порядка — это функция, которая может принимать другую функцию в качестве аргумента или возвращать другую функцию в качестве результата.
Функции первого класса

Вы, наверное, слышали, что JavaScript относится к функциям, как к объектам первого класса. Это выражение всего лишь означает, что в JavaScript функции имеют тот же статус, что и объекты: обладают типом Object; их можно задавать как значение переменной; их можно передавать и возвращать по ссылке, как любые другие переменные.

Это нативное свойство дает JavaScript особые возможности в функциональном программировании. Так как функции являются объектами, язык поддерживает естественный подход к функциональному программированию. На самом деле, этот подход так естественен, что держу пари, вы наверняка использовали его и не задумывались об этом.

Принимаем функции как аргументы

Если вы много занимались web-ориентированным Javascript программированием или браузерной разработкой, вы вероятно сталкивались с функциями, которые используют функция обратного вызова( callback — прим. переводчика). Функция обратного вызова — функция, которая выполняется в конце операции, когда все остальные операции уже завершены. Как правило, функция обратного вызова передается в качестве последнего аргумента функции. Часто функцию обратного вызова определяют как анонимную функцию.

Так как JavaScript является однопотоковым языком программирования, то есть операции выполняются поочередно, каждая операция выстраивается в очередь на поступление в единый поток. Стратегия передачи функции на выполнение, когда остальные родительские функции завершены, является одной из основных характеристик языков программирования, поддерживающих функции высшего порядка. Так, можно получить асинхронное поведение, то есть скрипт, ожидая результат, все же продолжит выполняться. Возможность передачи функций обратного вызова важна при работе с ресурсами, которые могут возвратить результат через неопределенный промежуток времени.

Такой подход очень удобен в среде веб-разработки, когда скрипт может отослать Ajax запрос на сервер, а затем обработать ответ вне зависимости от времени его получения, учета сетевых задержек и времени обработки на сервере. Node.js часто обращается к функциям обратного вызова для наиболее эффективного использования серверных мощностей. Данный подход также эффективен, когда приложений ожидает ввода данных от пользователя до начала выполнения функции.

Например, обратите внимание на сниппет простого JavaScript кода ниже, который добавляет обработчик событий к кнопке:

<button id="clicker">So Clickable</button>
 
<script>
  document.getElementById("clicker").addEventListener("click", function() {
    alert("you triggered " + this.id);
  });
</script>

Данный скрипт использует анонимную функцию для отображения предупреждения. Однако, он мог с тем же успехом использовать отдельно заданную функцию и передавать эту названную функцию методу addEventListener:
var proveIt = function() {
  alert("you triggered " + this.id);
};
 
document.getElementById("clicker").addEventListener("click", proveIt);

Обратите внимание, что мы передали proveIt, а не proveIt() нашей функции addEventListener. Когда Вы передаете функцию по имени без круглых скобок, вы передаете объект функции. Когда же вы передаете ее с круглыми скобками, то передается результат выполнения функции.

Наша маленькая proveIt() функция is структурно независима от окружающего ее кода, всегда возвращая id эелемента на котором она сработала. Этот небольшой код может находится в любом контексте, в котором вы хотите показать предупреждение с id элемента и так же, может быть вызван любым обработчиком событий.

Возможность заменить отдельно заданную функцию, именованной функцией открывает целый мир возможностей. По мере того, как мы пытаемся создать чистые функции, которые не меняют внешние данные и каждый раз возвращают одни и те же данные при одном и том же вводе, мы получаем важный инструмент, который поможет нам создать библиотеку небольших, целевых функций, пригодных для использования в любом приложении.

Возвращаем функции как результаты

Кроме возможности принимать функции как аргументы, JavaScript позволяет функциям возвращать другие функции как результат. Это логично, так как функции это просто объекты, и могут быть возвращены, как и любое другое значение.

Но что значит, вернуть функцию как результат? Если задать функцию как обратное значение другой функции, то можно создать функции, которые могут быть использованы в качестве шаблонов для создания новых функций. Это открывает дверь в мир функциональный магии на JavaScript.

Например, предположим, что Вам настолько надоело читать об особости миллениалов (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.

Так, мы используем возможность функций JavaScript не зависеть от изначально заданного количества аргументов и принимать любое их количество. Если аргумент отсутствует, то функция просто будет рассматривать его как незаданный.

С другой стороны, этот дополнительный аргумент можно передать позже, когда запрашиваемую функцию задают так, как мы описали выше, а именно: в качестве ссылки на функцию, возвращенную от другой функции с одним и большим количеством незаданных аргументов.

Пройдитесь по тексту еще раз, если вы не поняли, как работают функции высшего порядка. Мы создаем шаблон функции, которая возвращает другую функцию. Затем, мы задаем эту только что возвращенную функцию, исключив один атрибут, как кастомную имплементацию шаблона функции. Все созданные этим образом функции наследуют один и тот же код из шаблона функции, однако могут по умолчанию получить различные аргументы.

Вы уже применяете функции высшего порядка

Функции высшего порядка лежат в самой основе JavaScript, так что вы их уже используете. Каждый раз, когда вам надо передать анонимную функцию или функцию обратного вызова, вы работаете со значением, которое возвращает передаваемая функция, и используете его в качестве аргумента для другой функции.

Способность функций возвращать другие функции делает JavaScript чрезвычайно удобным и позволяет нам создавать кастомные функции для выполнения специфических задач с повсеместно используемым шаблоном функции. Каждая из этих маленьких функций получает все улучшения, которые появляются в коде шаблона функции. Это позволяет избежать дублей кода, а также сделать код более чистым и читабельным.

В качестве дополнительного бонуса: если вы убедитесь, что у функций нет побочного эффекта, так что они не изменяют значения, а всегда возвращают их неизменными для любого ввода данных, у вас появляется отличная возможность создавать наборы тестов и проверять, что изменения в коде функции шаблона не приводят к ошибкам.

Просто подумайте, как вы сможете использовать такой подход в собственных проектах. Преимущество JavaScript в том, что вы можете добавлять новые функциональности в уже используемый код. Попробуйте поэкспериментировать. вы удивитесь, как пара простейших манипуляций с функциями высшего порядка могут улучшить ваш код.

Об авторе

М. Дэвид Грин (M. David Green)
Я работал веб-разработчиком, писателем, менеджером по коммуникациям и директором отдела маркетинга в таких компаниях, как Apple, Salon.com, StumbleUpon и Moovweb. Мое исследование “Социология в телекоммуникации”, которое я провожу в Калифорнийском университете в Беркли, а также степень магистра по бизнесу по теме “Организационное поведение”, стали достаточным основанием понять, что человек движим инстинктом общения с себе подобными, что этот инстинкт достаточно силен и работает всегда и везде, вне зависимости от среды общения.

Над переводом работали: greebn9k(Сергей Грибняк), silmarilion(Андрей Хахарев)
Singree