В JavaScript, функции являются “функциями первого класса”, соответственно они могут:
- храниться в переменной, объекте или массиве.
- передаваться как аргументы в другую функцию.
- и даже могут быть возвращены из функции.
Хранение функций
В JavaScript функции могут храниться тремя способами:
- храниться в переменной:
let fn = function doSomething() {}
- храниться в объекте :
let obj = { doSomething : function(){} }
- храниться в массиве :
arr.push(function doSomething() {})
В первом и третьем примере я использовал так называемое “функциональное выражение” (Function Expression).
“Функциональные выражения” определяют функцию как часть большего выражения. Это когда строка кода начинается не с ключевого слова function.
В случае, если мы напишем строку с ключевого словаfunction
,то это будет “определение функции” (Function Declaration) и к функции будет применятся “поднятие функций”(hoisting).
Функции как аргументы
В следующем примере функцияdoSomething
отправляется в виде аргумента в функциюdoAction()
.
doAction(function doSomething(){});
doSomething
— это “функция обратного вызова”.
Функции обратного вызова передаются в качестве “коллбека”(от “callback“) в качестве аргумента в другие функции. Коллбеки часто встречаемая практика в мире Javascript.
Функции высшего порядка
Функции высшего порядка — это функции, которые принимают в качестве аргументов другие функции, либо возвращают функцию. А также могут делать оба действия одновременно.
Давайте взглянем на встроенные методы, которые как раз являются функциями высшего порядка : filter()
, map()
и reduce()
.
filter()
вызывается на массиве и принимает функцию в качестве первого аргумента, которая определяет условие фильтрации, которое может быть бесконечно гибким.
let numbers = [1,2,3,4,5,6]; function isEven(x){ return x % 2 === 0; } let evenNumbers = numbers.filter(isEven); //2 4 6
map()
превращает список значений в иные(модифицированные вами в переданной функции) значения, не изменяя оригинальный массив.
let numbers = [1,2,3,4,5,6]; function double(x){ return x*2; } let doubleNumbers = numbers.map(double); //2 4 6 8 10 12
reduce()
аккумулирует все значения коллекции в единое значение. Второй аргумент может быть любым доступным типом в Javascript.
let numbers = [1,2,3,4,5,6]; function sum(total, value){ return total + value; } let total = numbers.reduce(sum, 0); //21
Как вы можете заметить, работать с коллекциями в функциональном стиле гораздо проще.
А теперь давайте сами создадим функцию высшего порядка doWithLoading()
. Она будет принимать функцию в качестве параметра и исполнять её. Перед началом исполнения мы выведем сообщение о начале исполнения, а по завершению о том, что функцию выполнена.
function doWithLoading(fn){ console.log("начало загрузки"); let returnValue = fn(); console.log("конец загрузки"); return returnValue; } function process() { console.log("загружаем..."); } doWithLoading(process); // начало загрузки // загружаем... // конец загрузки
Функции возвращающие функцию
Ниже пример функции, которая возвращает другую функцию:
function createGenerator(){ return function generateNewID(){} }
Замыкание
Вот пример функции, createGenerator()
которая создает другую функциюgenerateNewID()
с приватным состоянием. Каждый раз когда мы вызываем createGenerato
,мы запоминаем индекс последнего вызова и возвращаем строку с номером текущего вызова.
function createGenerator(prefix){ let index = 0; return function generateNewID(){ index++; return prefix + index.toString(); } } let generateNewID = createGenerator("вызов номер: "); console.log(generateNewID()); //вызов номер: 1 console.log(generateNewID()); //вызов номер: 2 console.log(generateNewID()); //вызов номер: 3
Используя замыкания, теперь мы умеем создавать функции с приватным состоянием.
Декораторы
Декораторы — это функции высшего порядка, которые принимают функцию в виде аргумента и возвращают другую функцию. Возвращаемая функция — это модифицированная функция с аргумента. Но, фишка в том, что мы не изменяем код оригинальной функции.
Самый простой декоратор, который может быть найден в популярной библиотеке, например underscore.js или lodash.js — это функцияonce()
. Она кэширует результат вызова функции и при повторном вызов возвращает данные с кэша, не вычисляя их повторно. В мире ООП обычно называется Синглтон. Ниже вы можете увидеть имплементацию данного декоратора.
function once(fn){ let returnValue; let canRun = true; return function runOnce(){ if(canRun) { returnValue = fn.apply(this, arguments); canRun = false; } return returnValue; } } var processonce = once(process); processonce(); //process processonce(); //
Функции-декораторы мощная штука для создания произвольного поведения уже существующих функций без изменения кода оных.
Свойства функций
Функция — это объект, поэтому она может иметь свои собственные свойства, например name
и length
или методыtoString()
, bind()
, apply()
и call()
.
let log = function logMessage(message) { console.log(message); }
console.log(log.name); //logMessage console.log(log.length); //1 console.log(log.toString());
Частичное применение — это предоставление для функции меньшего количества аргументов, чем она ожидает.
Метод bind()
является таким примером. Обратите внимание на первый аргумент, который ожидаетthis.
function log(level, message){}
var logInfo = log.bind(null, "Info"); logInfo("message");
Заметили, как мы создали функциюlogInfo()
? Теперь она принимает только один аргумент(message
), минуя this
.
Лямбда функции
Лямбда функции — это функции, которые используются как значение.
Eric Elliott in Programming JavaScript Applications
Поскольку в JavaScript, функции являются объектами первого класса, их можно использовать как лямбда функции. Лямбда функции могут быть именованные и безымянные. Мне кажется, что вам привычней использовать именованные функции.
Ниже пример renderFirstItem
функции использованной как лямбда:
function renderFirstItem(){ /* code */ } $("#first").click(renderFirstItem);
Заключение
Функции первого класса и Замыкания, концепции которые предоставляют возможность писать в функциональном стиле. В результате мы можем использовать функции высшего порядка для удобной работы с массивами, а также использовать их в виде декораторов.
Также функции первого класса позволяют нам абстрагировать некий функционал и работать с функцией как с примитивом.
В JavaScript функции тоже объекты. А объекты могут иметь методы, например call
, apply
, bind
,которые могут использовать частичное применение функций чтобы установить контекст this.
Лучшая вещь в JavaScript — это имплементация функций. Это дает возможность делать всё правильно.
Douglas Crockford in JavaScript The Good Parts
Перевод статьи Cristi Salcescu: Discover the power of first class functions