Функции высшего порядка в JavaScript
Если вас напрягает или пугает термин "Функции высшего порядка", то эта статья 100% будет вам полезна!
Я постараюсь объяснить что это такое, почему это может быть полезно и как, собственно говоря, писать и использовать эти "Функции высшего порядка".
Возможно, термин "функции высшего порядка" звучит немного сложно и непонятно, но на самом деле это не так. Основная цель функций высшего порядка - внести в JavaScript поддержку функционального программирования.
JavaScript поддерживает такие функции "из коробки", а значит, мы просто обязаны использовать эти возможности для создания чистого высокопроизводительного кода, который легко переиспользовать.
Функциональное программирование?
Чтобы в полной мере понять концепцию функций высших порядком, для начала нужно разобраться с функциональным программированием и концепцией функций первого класса.
Функциональное программирование — это способ программировать, при котором вы можете передавать функции в качестве параметров другим функциям, а также возвращать их как значения.
В функциональном программировании вам нужно быть функцией и думать как функция мы думаем и пишем код с точки зрения функций.
Функции первого класса
Особенностью функционального программирования на JavaScript является то, что функции являются "гражданами первого класса". Это означает, что вы можете делать с ними все, что придет в голову, например, передавать их как аргументы другим функциями, изменять значение их свойств или возвращать из других функций.
Функции являются объектами, как и в других языках функционального программирования, но JavaScript функции являются особым типом объектов. Это Function объекты.
// простая JS-функция
const firstClassFun = input => input;
// возвращаем функцию из другой функции
const myFunctionAsOutput = function () {
return firstClassFun;
};
firstClassFun('value'); // => 'value'
myFunctionAsOutput()('value'); // => 'value'firstClassFun – это пример стрелочной функции, которая принимает некоторое значение в качестве аргумента и его же возвращает, не изменяя.
myFunctionAsOutput не принимает никаких параметров, а просто возвращает переменную firstClassFun, которая содержит функцию.
По сути вызов myFunctionAsOutput() – это то же самое, что и прямое обращение к firstClassFun – эти выражения взаимозаменяемы. Поэтому мы можем вызвать результат работы myFunctionAsOutput() как обычную функцию с помощью круглых скобок.
Посмотрим на еще один пример, в котором функция выступает как параметр другой функции:
// принимаем функцию как аргумент в другую функцию и возвращаем ее
const myFunctionAsInputAndOutput = function(inputFn) {
return inputFn;
};
myFunctionAsInputAndOutput(firstClassFun)('value'); // => 'value'Функция myFunctionAsInputAndOutput принимает функцию и ее же возвращает. По сути это просто еще один пример функции идентичности. То есть вызов myFunctionAsInputAndOutput(firstClassFun) – это то же самое, что и прямое обращение к firstClassFun, значит, мы тоже можем его вызвать.
То же самое можно сделать и с firstClassFun, если в качестве параметра input передать функцию.
firstClassFun(firstClassFun)('value'); // => 'value'Выражение firstClassFun(firstClassFun) возвращает саму функцию firstClassFun, которую вновь можно вызвать
Единственная разница между двумя функциями идентичности firstClassFun и myFunctionAsInputAndOutput состоит в том, что первая – это стрелочная функция, а вторая – обычная.
Функции высшего порядка
В функциональном программировании функция высшего порядка (higher-order function) – это самая обычная функция, которая оперирует другими функциями: принимает их как входные параметры или возвращает в качестве выходных.
Таким образом, все функции из предыдущего примера – это функции высшего порядка.
Вложенные функции (одна функция создается внутри другой) всегда имеют доступ к области видимости родительской функции, которая невидима снаружи. Таким образом, функция возвращенная из другой функции, может работать с данными, которые больше никому не видны. Это называется замыканием.
Давайте напишем что-то вроде упрощенного варианта мемоизации:
const memo = fn => {
// memory доступна только внутри функции
let memory = [];
return anything => {
// область видимости вложенной функции
if(anything in memory) {
// вложенная функция читает переменную memory
return memory[anything];
} else {
const result = fn(anything);
// вложенная функция изменяет переменную memory
memory[anything] = result;
return result;
}
};
};
const reverse = memo(a => a.split('').reverse().join(''));
upperCase('abc'); // => 'cba'Мемоизация – это техника оптимизации вычислений в функциональном программировании. Она сохраняет результаты предыдущих вычислений и использует их многократно (не тратя времени на вычисление при таких же параметрах функции).
Например, у нас есть функция, которая разворачивает строку:
const reverse = memo(a => a.split('').reverse().join('')); Мы хотим мемоизировать ее (предположим, что развернуть строку – это очень трудозатратная операция). Мы передаем эту функцию в функцию высшего порядка memo и получаем взамен другую функцию, которую сохраняем в переменную:
const reverse = memo(a => a.split('').reverse().join('')); Новая функция работает точно так же, как и старая: она принимает строку и возвращает ту же строку в перевернутом виде. Но под капотом используется техника мемоизации. Если входные данные повторяются, вычисление не происходит заново. Вместо этого берется ранее сохраненное значение из кеша memory.
На этом сегодня все, надеюсь вы узнали для себя что-то новое ✌️
Чтобы найти больше статей по JavaScritp -> подписывайтесь на телеграмм канал и ищите статьи по хештегу #научите_меня_js!