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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more