В 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

Предыдущая статьяБаланс между креативностью и удобством
Следующая статья5 видов регрессии и их свойства