В JavaScript любая функция - это объект, и следовательно, ею можно манипулировать как объектом, в частности:
- передавать как аргумент и возвращать в качестве результата при вызове других функций (функций высшего порядка);
- создавать анонимно и присваивать в качестве значений переменных или свойств объектов.
Таким образом, в JavaScript функции могут:
- храниться в переменной, объекте или массиве;
- передаваться как аргументы в другую функцию;
- возвращаться из других функций (функций высшего порядка).
Функции, обладающие указанными свойствами, в JavaScript называют «функциями первого класса».
Функции первого класса – это специальный тип объектов (Function-объект), позволяющий формализовать средствами языка определённую логику поведения и обработки данных, и который можно задавать как значение переменной, передавать и возвращать по ссылке как любые другие переменные.
Функция высшего порядка (Higher-Order Function) - это функция, которая оперирует другими функциями и может:
- принимать другую функцию в качестве аргумента или
- возвращать другую функцию в качестве результата.
В JavaScript функции могут храниться тремя способами:
1 2 3 4 5 6 7 8 |
// в переменной: let fn = function doSomething() {}; // в объекте : let obj = { doSomething : function(){} }; // в массиве : arr.push(function doSomething() {}); |
Что такое функциональное программирование
Функциональное программирование — это способ программирования, позволяющий передавать функции в качестве параметров другим функциям, а также возвращать их как значения.
Читайте также Шпаргалка по функциональному программированию
Функции первого класса
В JavaScript функции являются особым типом объектов - Function объектами, которым в ходе исполнения кода можно присваивать случайные свойства. Например:
1 2 3 4 5 6 7 |
function greeting() { console.log('Hello World'); } greeting(); // 'Hello World' greeting.lang = 'English'; // добавим свойство lang к функции greeting() console.log(greeting.lang); // English - выведем добавленное свойство |
Несмотря на то, что приведенный выше код валидный, его не рекомендуется использовать на практике. Не следует присваивать случайные свойства функциональным объектам. Для этого используйте объекты.
Функции высшего порядка в JS
Функции высшего порядка оперируют другими функциями, принимая их в качестве аргументов или возвращая их. Функции высшего порядка позволяют JavaScript быть пригодным для функционального программирования.
Некоторые функции высшего порядка встроены в язык, например, Array.prototype.map, Array.prototype.filter и Array.prototype.reduce.
Функции как аргументы
Функция обратного вызова (callback) - это функция, переданная в функцию высшего порядка в качестве аргумента и вызываемая в коде функции высшего порядка (например, по завершению какого-либо действия).
1 2 3 4 5 6 7 8 |
function greeting(name) { alert('Hello ' + name); } function processUserInput(callback) { var name = prompt('Please enter your name.'); callback(name); } processUserInput(greeting); // greeting - функция обратного вызова |
В следующем примере функции nameLn и nameUpCase являются аргументами функции mapArr:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const arr = ["Alex", "Olga", "Sergio", "Mary"]; function mapArr(arr, fn) { let resArr = []; for (let i = 0; i < arr.length; i++) { resArr.push(fn(arr[i])); } return resArr; } function nameLn(el) { return el.length; } function nameUpCase(el) { return el.toUpperCase(); } console.log(mapArr(arr, nameLn)); // функция nameLn является аргументом функции mapArr console.log(mapArr(arr, nameUpCase)); // функция nameUpCase является аргументом функции mapArr |
Обратите внимание на синтаксис: при передаче функции в качестве параметра скобки () не указываются ( т.е. в параметре функция не вызывается, например, function mapArr(arr, fn) или mapArr(arr, nameLn) ).
Метод filter() вызывается на массиве и принимает в качестве первого аргумента функцию, которая определяет условие фильтрации.
1 2 3 4 5 6 7 8 9 |
let numbers = [1, 2, 3, 4, 5, 6]; function isEven(x) { // функция, задающая условие фильтрации return x % 2 === 0; } let evenNumbers = numbers.filter(isEven); // 2 4 6 console.log(evenNumbers) // Array(3) [ 2, 4, 6 ] console.log(typeof evenNumbers) // object |
Метод map() превращает список значений в иные (модифицированные в переданной функции) значения, не изменяя оригинальный массив.
1 2 3 4 5 6 7 8 9 |
let numNew = [1, 2, 3, 4, 5, 6]; function double(x) { // функция, изменяющая (модифицирующая) значения return x * 2; } let doubleNumbers = numNew.map(double); // 2 4 6 8 10 12 console.log(doubleNumbers); // Array(6) [ 2, 4, 6, 8, 10, 12 ] console.log(typeof doubleNumbers); // object |
Метод reduce() аккумулирует все значения коллекции в единое значение. Второй аргумент может быть любым доступным типом в Javascript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let numbersNew = [1, 2, 3, 4, 5, 6]; function sum(total, value) { return total + value; } let total = numbersNew.reduce(sum, 0); console.log(total); // 21 console.log(typeof total); // number // ИЛИ let numbersNew = [1, 2, '3', 4, 5, 6]; function sum(total, value) { return total + value; } let total = numbersNew.reduce(sum, 0); console.log(total); // 33456 console.log(typeof total); // string |
Функции, возвращающие функцию
Ниже пример функции, которая возвращает другую функцию:
1 2 3 |
function createGenerator(){ return function generateNewID(){} } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function greeting(firstName) { return function(lastName) { return `Hello, ${firstName} ${lastName}`; }; } const first = greeting("Alex"); console.log(first); // function greeting() console.log(first("NAV")); // Hello, Alex NAV // ИЛИ иная запись для получения аналогичного результата console.log(greeting("Alex")("NAV")); // Hello, Alex NAV |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function minus(firstNum) { if (!firstNum) firstNum = 0; return function(lastNum) { if (!lastNum) lastNum = 0; return (firstNum - lastNum); }; } minus(10)(6); // 4 minus(5)(6); // -1 minus(10)(); // 10 minus()(6); // -6 minus()(); // 0 |
- Строка 1. Мы объявляем переменную с именем minus в глобальном контексте выполнения и назначаем ей определение функции c параметром firstNum. Строки 2-7 описывают указанное определение функции. В текущий момент тело этой функции не выполняется. Просто сохраняем определение функции в переменной minus.
- Строка 9. Скобки () указывают, что нужно выполнить или вызвать функцию с именем minus которая была создана на шаге 1, и передать ей параметр 10:
- вызов функции minus (теперь мы находимся в строке 1);
- создается новый локальный контекст выполнения, мы можем создавать локальные переменные в новом контексте выполнения;
- движок добавляет новый контекст в стек вызовов. В контексте функции minus объявляется переменная firstNum;
- переменной firstNum присваивается значение 10. Переходим к телу функции.
- Строка 2. Значение переменной firstNum проверяется на пустоту. Если передано пустое значение, переменной firstNum присваивается значение 0.
- Строка 3. Функция minus возвращает анонимную функцию с аргументом lastNum. Переходим в строку 9.
- Строка 9. Скобки у функции minus(10)() указывают, что нужно выполнить или вызвать возвращенную ею анонимную функцию, и передать ей параметр (6):
- вызов анонимной функции (теперь мы находимся в строке 3);
- создается новый локальный контекст выполнения (внутри контекста функции minus), мы можем создавать локальные переменные в новом контексте анонимной функции;
- движок добавляет новый контекст в стек вызовов. В контексте анонимной функции объявляется переменная lastNum;
- переменной lastNum присваивается значение 6. Переходим к телу функции.
- Строка 4. Значение переменной lastNum проверяется на пустоту. Если передано пустое значение, переменной lastNum присваивается значение 0.
- Строка 5. Выполняется вычитание содержимого переменной firstNum (найденного движком в контексте функции minus) и содержимого переменной lastNum. Результат операции вычитания (4) возвращается из анонимной функции. Переходим на строку 9.
- Локальный контекст выполнения уничтожается, он удаляется из стека вызовов, переменные firstNum и lastNum больше не существуют.
Замыкание функции
Подробнее см. Замыкание в JavaScript
Замыкание функции — это комбинация функции и лексического окружения, в котором эта функция была определена; замыкание обеспечивает доступ внутренней функции к области видимости (Scope) внешней функции (при этом переменные внутренней функции для внешнего окружения недоступны).
Когда функция вызывает другую функцию, каждой них достаётся собственный участок стека. Здесь хранятся все их локальные переменные и указатель команд, который хранит данные о том, где был выполнен вызов. Когда функция завершает работу, блоки памяти, занятые ей, снова делаются доступными для других целей.
Таким образом, выполняющуюся функцию можно рассматривать как объект в памяти; переменные функции - это свойства этого объекта. Когда обычная функция завершает свое выполнение, то освобождает память, которую занимала, если на её переменные не осталось ссылок.
Внутренняя функция может использовать свойства объекта внешней функции, что препятствует удалению объекта (переменных) внешней функции из памяти (внешняя переменная "замыкается" внутренней функцией).
Замыкание позволяет:
- ограничить доступ к данным (ограничить их область видимости);
- создать своеобразное автономное хранилище данных.
Ниже пример функции createGenerator(), которая создает другую функцию genNewID() с приватным состоянием. Каждый раз когда мы вызываем createGenerator(), функцией genNewID() запоминается индекс последнего вызова index и возвращается строка с номером текущего вызова.
1 2 3 4 5 6 7 8 9 10 11 |
function createGenerator(prefix) { let index = 0; return function genNewID() { index++; // значение переменной, объявленной во внешней функции, меняется, но вне функции genNewID() оно недоступно (изменить его нельзя) return prefix + index.toString(); }; } let generateNewID = createGenerator("вызов номер: "); console.log(generateNewID()); //вызов номер: 1 console.log(generateNewID()); //вызов номер: 2 console.log(generateNewID()); //вызов номер: 3 |
Декораторы
Декораторы — это функции высшего порядка, которые принимают функцию в виде аргумента и возвращают другую функцию. Возвращаемая функция - это модифицированная функция с аргумента (при этом код передаваемой оригинальной функции не изменяется).
В простейшем виде декоратор — это способ оборачивания одного фрагмента кода в другой.
Подробнее о декораторах
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function myGreeting(name) { console.log("Hello, " + name); } function logFunction(wrapped) { return function() { console.log("Start"); const result = wrapped.apply(this, arguments); console.log("Finish"); return result; }; } const wrap = logFunction(myGreeting); myGreeting("Alex"); // Hello, Alex wrap("Alex"); // Starting // Hello, Alex // Finished |
Создаётся новая функция, которая назначается константе wrap. Эта функция может быть вызвана точно так же, как и функция doGreeting, и делать она будет то же самое. Разница заключается в том, что до и после вызова оборачиваемой функции будет выполнено логирование, выводящее сообщения // Starting и // Finished.
Таким образом, обёртка-декоратор всего лишь добавляет к поведению функции некоторые дополнительные действия (например, логирование или кеширование).
Функции-декораторы - мощное средство для создания произвольного поведения уже существующих функций без изменения их кода.