Published
Edited
Nov 21, 2021
1 fork
Insert cell
# Функции и функциональное программирование в JavaScript
Insert cell
# Содержание:
* [Создание функций](#funcCreate)
* [Функции высшего порядка](#HOFunctions)
* [this](#_this)
* [call](#call)
* [bind](#bind)
* [Каррирование](#curry)
Insert cell
funcCreate = md`# Создание функций`
Insert cell
JavaScript - язык с сильным функциональным уклоном. Функции JavaScript **параметризированы**: определение функции может включать список идентификаторов, известных как **параметры**, которые выступают в качестве локальных переменных для тела функции. При вызове функций для параметров предоставляются значения (**аргументы**).
Insert cell
// Простая функция сложения двух чисел, которая принимает на вход 2 параметра
function sum(a, b) {
return a + b;
}
Insert cell
Подобный вид объявления функции назывывается **function declaration**
Insert cell
Существует также способ задать функцию через **function expression**:
Insert cell
sub = function (a, b) {
return a - b;
}
Insert cell
Через синтаксис **стрелочной функции**:
Insert cell
mult = (a, b) => {
return a * b;
}
Insert cell
А также с помощью конструктора класса `Function`
Insert cell
divide = new Function('a', 'b', 'return a / b');
Insert cell
// Вызвали функцию один раз с параметрами 2 и 2
sum(2, 2);
Insert cell
// Вызвали ту же самую функцию ещё раз с параметрами 3 и 3
sum(3, 3);
Insert cell
Присутствует возможность отличать функции от остальных типов при помощи оператора `typeof`
Insert cell
typeof sum;
Insert cell
Но следует знать, что сама функция является объектом. А это значит, что ему можно присваивать свойства
Insert cell
{
sum.definition = "Adds two numbers";
return sum.definition;
}
Insert cell
HOFunctions = md`# Функции высшего порядка`
Insert cell
Некоторые методы класса `Array` являются **функциями высшего порядка**
Insert cell
// Определим простую функцию, которая принимает один параметр и качестве результата возвращает этот параметр умноженный на 2
function multiplyByTwo(a) {
return a * 2;
}
Insert cell
{
const arr = [1, 2, 3];
// Не забываем, что метод map() создаёт новый массив, а не изменяет старый!
const multipliedArr = arr.map(multiplyByTwo);
return multipliedArr;
}
Insert cell
Довольно часто встречается потребность передавать функцию в качестве аргумента какой-то другой функции. Создавать отдельную функцию для каждого такого случая не очень удобно, причем чаще всего такая функция нужна лишь в одном единственном месте. В таком случае лучше использовать **анонимную функцию**
Перепишем пример выше с использованием анонимной функции
Insert cell
{
const arr = [1, 2, 3];
// JS позволяет записывать определение функций, избегая разрывов строки!
const multipliedArr = arr.map(function(elem) { return elem * 2; });
// Точно такой же результат
return multipliedArr;
}
Insert cell
[Подробнее про метод `map()`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
Insert cell
Также можно использовать стрелочную функцию
Insert cell
{
const arr = [1, 2, 3];
// JS позволяет записывать определение функций, избегая разрывов строки!
const multipliedArr = arr.map((elem) => { return elem * 2; });
// Точно такой же результат
return multipliedArr;
}
Insert cell
_this = md`# this`
Insert cell
Выражения стрелочных функций имеют более короткий синтаксис по сравнению с функциональными выражениями и **лексически привязаны** к значению `this`. Что это значит? Разберем следующий пример
Insert cell
{
const group = {
title: "ICS-71B",
students: ["Suvorov", "Kovalenko", "Tyurin"],
result: [],
createResult() {
this.students.forEach(function(student) {
// TypeError: undefined is not an object (evaluating 'this.result')
this.result.push(this.title + ': ' + student);
});
}
};
group.createResult();
return group.result;
}
Insert cell
Ошибка возникает потому, что `forEach` по умолчанию выполняет функции с `this`, равным `undefined`, и в итоге мы пытаемся обратиться к undefined.result (и undefined.title).
Insert cell
{
const group = {
title: "ICS-71B",
students: ["Suvorov", "Kovalenko", "Tyurin"],
result: [],
createResult() {
this.students.forEach((student) => {
// Всё хорошо, произошла лексическая привязка к this!
this.result.push(this.title + ': ' + student);
});
}
};
group.createResult();
return group.result;
}
Insert cell
Здесь внутри `forEach` использована **стрелочная функция**, таким образом `this.result` и `this.title` в ней будет иметь точно такое же значение, как в самом методе `showList`.
Insert cell
Разберем ещё один пример с **this** и свойством объекта
Insert cell
{
// Корзина покупателя
const cart = {
items: [],

color: 'grey',
}

// Функция добавления товара в корзину
function addItemToCart(item) {
this.items.push(item);
}

// TypeError: undefined is not an object (evaluating 'this.items')
// addItemToCart('milk');

// Добавим функцию addItemToCart как свойство объекта cart
cart.addItemToCart = addItemToCart;
// И вызовем метод объекта
cart.addItemToCart('milk');
cart.addItemToCart('spaghetti');
return cart.items
}
Insert cell
Подробнее про стрелочные фунции:
* https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Functions/Arrow_functions
* https://learn.javascript.ru/arrow-functions
Insert cell
Помним, что **функция это в первую очередь объект**. И как у всякого объекта - у неё могут быть свои методы (и они есть)
Insert cell
call = md`# call`
Insert cell
Метод `call()` вызывает функцию с указанным значением this и индивидуально предоставленными аргументами.
Insert cell
{
// Объект с полями возраста и цели на жизнь, а также методом "взросления"
const Ivan = {
age:15,
goal: "Graduate from BMSTU",

// Метод "взросления"
growUp(newGoal) {
this.age++;
this.goal = newGoal;
}
}

// Объект, у которого есть только возраст и цель, но нет метода "взросления"
const Nikita = {
age: 20,
goal: "Work at Roscosmos",
}

Ivan.age; // 15
Ivan.growUp("Become Intel software engineer"); // У Вани день рождения! Увеличим его возраст и зададим новую цель.
Ivan.age; // 16

Nikita.age; // 20
// TypeError: Nikita.growUp is not a function. (In 'Nikita.growUp()', 'Nikita.growUp' is undefined)
// Nikita.growUp();
// У Никиты нет метода "взросления"!

// Что же делать? Воспользуемся методом call()!
// Вызовем метод call у функции growUp и привяжем значение this к объекту Nikita
Ivan.growUp.call(Nikita, "Become Avito Golang Developer");
return Nikita; // Никита повзрослел и задал новую цель на жизнь!
}
Insert cell
[Подробнее про метод `call()`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/call)
Insert cell
bind = md`# call`
Insert cell
Метод `bind()` **создаёт новую функцию**, которая при вызове устанавливает в качестве контекста выполнения `this` предоставленное значение.
Insert cell
{
let user = {
firstName: "Vasya"
};
function func() {
return this.firstName;
}

// Внимание! Мы только создали функцию, но не вызывали её!
let funcUser = func.bind(user);
// Вызов новой функции
return funcUser();
}
Insert cell
function mul(a, b) {
return a * b;
}
Insert cell
Мы можем привязать не только `this`, но и аргументы. Это делается редко, но иногда может быть полезно.
Insert cell
{
// Воспользуемся bind, чтобы создать функцию double на её основе:
const double = mul.bind(null, 2);

return [
double(3), // = mul(2, 3) = 6
double(4), // = mul(2, 4) = 8
double(5), // = mul(2, 5) = 10
]
}
Insert cell
Код выше является одним из примеров **частичного применения**. [Подробнее про частичное применение](https://frontend-stuff.com/blog/partial-application/)
Insert cell
[Подробнее про метод `bind()`](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)
Insert cell
curry = md`# Каррирование`
Insert cell
Каррирование – продвинутая техника для работы с функциями. Она используется не только в JavaScript, но и в других языках. Это трансформация функций таким образом, чтобы они принимали аргументы не как **f(a, b, c)**, а как **f(a)(b)(c)**. Каррирование не вызывает функцию. Оно просто трансформирует её.
Insert cell
{
function curry(f) { // curry(f) - функция высшего порядка, выолняющая каррирование
return function(a) { // curry(f) возвращает функцию, принимающую один аргмуент
return function(b) { // которая возвращает функцию, принимающую один аргмуент
return f(a, b); // которая... возвращает результат вызова исходной функции с двумя переданным в анонимные функции параметрами!
};
};
}

function sum(a, b) {
return a + b;
}

// Создали новую функцию на основе функции суммы, принимающей 2 значения
let curriedSum = curry(sum);
return curriedSum(1)(2); // То же, что и sum(1, 2)
}
Insert cell
Выглядит немного страшно и запутанно. Разберём поподробнее:
* Результат curry(func) – обёртка function(a).
* Когда она вызывается как sum(1), аргумент сохраняется в лексическом окружении и возвращается новая обёртка function(b).
* Далее уже эта обёртка вызывается с аргументом 2 и передаёт вызов к оригинальной функции sum.
Insert cell
Ещё один из примеров использования **каррирования**, но уже в качестве **частичного применения**
Insert cell
{
function addBase(base){
return function(num) {
return base + num;
}
}

const addTwo = addBase(2);
return [
addTwo(2),
addTwo(3),
addTwo(4),
]
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more