Одной из характеристик JavaScript, которая делает его хорошо подходящим для функционального программирования, является тот факт, что он может принимать функции более высокого порядка. Функция более высокого порядка — это функция, которая может принимать другую функцию в качестве аргумента или которая возвращает функцию в результате.
Функции первого класса
Возможно, вы слышали, что в JavaScript-коде функции рассматриваются как первоклассные граждане. Это означает, что функции в JavaScript рассматриваются как объекты. У них есть тип Object
, они могут быть назначены в качестве значения переменной, и они могут быть переданы и возвращены, как и любая другая ссылочная переменная.
Эта нативная способность дает JavaScript особые возможности, когда дело доходит до функционального программирования. Поскольку функции являются объектами, язык поддерживает очень естественный подход к функциональному программированию. На самом деле, это так естественно, что я уверен, что вы использовали его, даже не задумываясь об этом.
Принимая функции в качестве аргументов
Если вы много занимались программированием на базе JavaScript на JavaScript или разработкой интерфейса, вы, вероятно, сталкивались с функциями, которые используют обратный вызов. Обратный вызов — это функция, которая выполняется в конце операции, когда все остальные операции завершены. Обычно эта функция обратного вызова передается как последний аргумент функции. Часто он определяется как анонимная функция.
Поскольку JavaScript является однопоточным, что означает, что одновременно выполняется только одна операция, каждая операция, которая должна произойти, ставится в очередь в этом единственном потоке. Стратегия передачи функции, которая должна быть выполнена после завершения остальных операций родительской функции, является одной из основных характеристик языков, поддерживающих функции высшего порядка. Это допускает асинхронное поведение, поэтому сценарий может продолжить выполнение в ожидании результата. Возможность передать функцию обратного вызова имеет решающее значение при работе с ресурсами, которые могут возвращать результат через неопределенный период времени.
Это очень полезно в среде веб-программирования, где сценарий может отправлять запрос Ajax на сервер, а затем должен обрабатывать ответ всякий раз, когда он поступает, не зная заранее о задержке в сети или времени обработки на сервере. Node.js часто использует обратные вызовы для наиболее эффективного использования ресурсов сервера. Этот подход также полезен в случае приложения, которое ожидает пользовательского ввода перед выполнением функции.
Например, рассмотрим этот фрагмент простого JavaScript, который добавляет прослушиватель событий к кнопке.
<button id="clicker">So Clickable</button> 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.
Например, представьте, что вы устали читать все эти статьи о специфике Millennials , и вы решили, что хотите заменить слово Millennials на фразу « Змеиные люди» каждый раз, когда это происходит. Ваш импульс может состоять в том, чтобы просто написать функцию, которая выполняла эту замену текста для любого текста, который вы ему передали:
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.
Это работает, но это довольно специфично для этой ситуации. Вы также устали слышать о бэби-бумеров . Вы также хотели бы создать для них пользовательскую функцию. Но даже с такой простой функцией вам не нужно повторять код, который вы написали:
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
, предварительно заполненной двумя первыми аргументами, которые она принимает, позволяет новой функции принимать любой передаваемый вами аргумент и использовать его в качестве исходного текста во внутренней функции, возвращаемой функцией attutide
,
Здесь мы пользуемся тем фактом, что функциям JavaScript не важно, передано ли им то же количество аргументов, что и для определения. Если аргумент отсутствует, функция будет обрабатывать отсутствующие аргументы как неопределенные.
С другой стороны, этот дополнительный аргумент может быть передан позже, когда вызываемая функция была определена так, как мы только что продемонстрировали, как ссылка на функцию, возвращенную из другой функции с аргументом (или более), оставленным неопределенным.
Пройдите несколько раз, если вам нужно, чтобы вы полностью поняли, что происходит. Мы создаем шаблонную функцию, которая возвращает другую функцию. Затем мы определяем эту вновь возвращаемую функцию, за исключением одного атрибута, как пользовательскую реализацию функции шаблона. Все функции, созданные таким образом, будут наследовать один и тот же рабочий код от функции шаблона, но могут быть предварительно определены с различными аргументами по умолчанию.
Это то, что вы уже делаете
Функции высшего порядка настолько важны для работы JavaScript, что вы уже используете их. Каждый раз, когда вы передаете анонимную функцию или обратный вызов, вы на самом деле берете значение, которое возвращает переданная функция, и используете его в качестве аргумента для другой функции.
Возможность функций возвращать другие функции расширяет удобство JavaScript, позволяя нам создавать пользовательские именованные функции для выполнения специализированных задач с общим шаблоном кода. Каждая из этих маленьких функций может наследовать любые улучшения, сделанные в исходном коде в будущем, что помогает нам избежать дублирования кода и сохраняет наш исходный код чистым и читаемым.
В качестве дополнительного бонуса, если вы убедитесь, что ваши функции чистые, чтобы они не изменяли внешние значения и всегда возвращали одно и то же значение для любого заданного входа, вы ставите себя в хорошую позицию для создания наборов тестов, чтобы убедиться, что ваши Изменения кода не нарушают ничего, на что вы полагаетесь при обновлении функций шаблона.
Начните думать о том, как вы можете воспользоваться этим подходом в своих собственных проектах. Одна из замечательных особенностей JavaScript состоит в том, что вы можете смешивать функциональные методы прямо с кодом, с которым вы уже знакомы. Попробуйте несколько экспериментов. Вы можете быть удивлены, насколько легко небольшая работа с функциями более высокого порядка может улучшить ваш код.