Перейти к основному содержимому

Функции в JavaScript

Разработчику Архитектору

Функции в JavaScript

Что такое функция в JavaScript?

Функция – это блок кода, который выполняет конкретную задачу и может быть повторно использован. Это как мини-программа внутри основной программы.

Как выглядит функция:

function имяФункции(аргументы) {
// Тело функции (код, который выполняется при вызове)
return результат; // Необязательно
}

Что здесь указано?

  • function – это ключевое слово, зарезервированное в языке JS для указания, что сейчас будет функция;
  • имяФункции – это любой набор слов и символов (начинать нужно строчной буквой, ни в коем случае не Прописной и не 1цифрой), который придумывает программист. Поменять можно в любое время, но если где-то идёт обращение к функции, важно поменять и обращение;
  • аргументы – это входные данные, которые принимает функция, словно пакет документов, требуемый в регистратуре – чтобы работать, функция должна знать исходные данные. Их можно и не указывать, тогда скобки с аргументами будут пустыми ();
  • тело функции – это код, который включает в себя почти любой набор кода, который представляет собой суть функции, её всю логику;
  • return – ключевое слово для указания, что сейчас будет возвращаемый результат.

Пример создания:

// Функция для сложения двух чисел
function sum(a, b) {
const result = a + b;
return result; // Возвращаем результат
}

Как можно заметить при разборе:

  • функция называется sum;
  • функция принимает аргументы a и b, следовательно, вызывая её, нужно будет указать значения, где первое значение до запятой будет равным a, второе – b;
  • код включает в себя константу (ключевое слово const) с именем result;
  • константа result равна a + b;
  • результат, который вернёт функция – это то, чему равна константа result.

image-4.png

Так, мы получаем некую функцию, которая суммирует аргументы. И если нам, в какой-то момент, понадобится сложить какие-то два значения, теперь мы можем просто вызывать нашу функцию, сообщим ей значения в виде аргументов.

Пример вызова:

const total = sum(2, 3); // Вызов функции с аргументами 2 и 3
console.log(total); // 5

Здесь мы выполняем какую-то логику, и объявляем константу с именем total, которая равна результату выполнения функции sum(a, b). Указав имя функции и значения аргументов, мы можем даже ссылаться на нашу константу, зная, что она всегда будет равна 5 в нашем случае. И выводим на консоль значение этой константы, выполнив console.log(total).

Ещё раз – повторим, что такое вызов. Понятно, что функция что-то выполняет в своём теле. Но она не запустится сама по себе, а значит кто-то должен её запустить. Этот «запуск» называется вызовом – функцию нужно вызвать. Вызов функции – это момент, когда функция начинает выполнять свой код. Вызов происходит по имени функции с круглыми скобками (и аргументами, если они есть):

// Создали функцию
function greet() {
console.log("Привет!");
}

// Вызвали её 3 раза
greet(); // "Привет!"
greet(); // "Привет!"
greet(); // "Привет!"

Вызывать функции можно сколько угодно раз и когда угодно. Именно тут и раскрывается смысл декомпозиции – нужно разбивать всё на подзадачи и для каждой создавать функции, которые могут пригодиться и упростить код.

Загрузка...

Так, мы имеем два понятия - объявление (создание) функции и её вызов.


Объявление функции

Способы объявления функции

В JavaScript существует три способа объявления функции:

  • Function Declaration Statement;
  • Function Definition Expression;
  • Стрелочные функции.

Function Declaration Statement

Function Declaration Statement - используется ключевое слово function, затем указывается имя функции, параметры, тело и возвращаемое значение:

function <имя> (<Параметры>) {
<тело>
return <возвращаемое значение>
}

Function Declaration - это способ объявления функции, при котором функция создается синтаксической конструкцией function, за которой следует имя функции. Объявление является статическим: оно обрабатывается движком JavaScript на этапе лексического анализа (до выполнения кода). Это означает, что функция становится доступной во всей области видимости (scope), где она объявлена, даже до того места в коде, где написано само объявление. Этот процесс называется поднятие (hoisting).

Function Declaration Statement - это термин из спецификации ECMAScript, обозначающий саму синтаксическую конструкцию function с именем, которая является отдельным предложением (statement) в коде. В отличие от выражения, предложение не возвращает значение само по себе; его цель — создать сущность (функцию) в текущей области видимости.

Предложение (Statement) стоит отдельно и выполняет действие (создание функции). Выражение (Expression) вычисляется и возвращает значение (которым может быть ссылка на функцию).


Function Definition Expression

Function Definition Expression - функция-выражение, или анонимная функция. Здесь подразумевается, что имени у функции нет:

function (<параметры>) {
<тело>
return <возвращаемое значение>
}

Это удобно, когда нужно создать «вспомогательную функцию», допустим, для передачи в качестве аргумента или записи в переменную. Можно сделать как переменную и получить похожий на вызов синтаксис:

let sum = function(a, b) { return a + b }
let x = sum(2, 2)

Function Definition Expression (Выражение определения функции / Function Expression) - это способ создания функции, при котором конструкция function используется как часть более крупного выражения. Функция создается динамически в момент выполнения кода (runtime), когда интерпретатор достигает этой строки. Она может быть анонимной (без имени) или именованной (имя доступно только внутри самой функции). Результатом этого выражения является ссылка на созданную функцию, которую можно присвоить переменной, передать в другую функцию или вернуть из другой функции.

Ключевые особенности:

  • Создается в момент выполнения (не поднимается вместе с остальным кодом).
  • Если присвоена переменной, доступна только после строки присваивания (вместо неё в памяти будет undefined).
  • Может быть анонимной.
  • Имя внутренней функции (если указано) видно только внутри тела этой функции.
  • Является частью выражения, поэтому может использоваться в любом месте, где ожидается выражение (аргументы функций, условия, массивы).

Стрелочные функции

Стрелочные функции - это сокращенный синтаксис для создания функций, появившийся в ES6 (ECMAScript 2015). Они являются разновидностью выражений (Function Expression), а не объявлений. Главная их особенность — отсутствие собственного контекста this. Значение this в стрелочной функции определяется не тем, как она вызывается, а той областью видимости, в которой она была определена (лексический this). Также они не имеют своих аргументов arguments (доступен через замыкание) и не могут использоваться как конструкторы (нельзя вызвать через new).

Стрелочные функции (или функции-стрелки) - анонимно, без фигурных скобок, с использованием «=>» стрелки:

let <имяПеременной> = (<параметры>) => <тело функции>;

Они позволяют сокращать синтаксис функций, заменяя «{ return result}» на «=> result». Это может быть сложно новичку, но их смысл проще, чем кажется:

Представим, что у нас есть a и b:

let a = 1;
let b = 2;

И нам нужно получить сумму, записав в результат:

let result = sum(a, b);

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

Мы объявили переменную result, которая равна результату выполнения функции sum, принимающей аргументы a и b, суммирующей их и возвращающей результат. Но можно её записать короче:

let result = (a, b) => a + b;

image-5.png

То есть мы указали аргументы, и буквально упростили функцию, указав её в одной строке. Но для новичка может быть не столь легко читать такие функции. А теперь, если не поняли, перечитайте и подумайте. Принцип таков:

переменная = (аргумент1, аргумент2) => выражение;

Ключевые особенности:

  • Лексический this: Наследует значение this из окружающего контекста. Идеально подходят для колбэков, обработчиков событий и методов объектов, где нужно сохранить ссылку на объект.
  • Нельзя использовать как конструктор: Вызов new () => {} вызовет ошибку.
  • Нет объекта arguments: Для доступа к аргументам нужно использовать остаточный параметр ...args.
  • Не имеет прототипа prototype: Нельзя добавлять методы в прототип.
  • Анонимность: Всегда являются выражениями, обычно присваиваются переменным.

С функциями можно делать многие другие вещи, но обо всём по порядку.


Особенности функций

Виды функций

Функции бывают нескольких видов:

  1. Чистая функция (Pure Function) – всегда возвращает одинаковый результат для одних и тех же аргументов, и не изменяет внешние переменные.
// Чистая функция
function multiply(a, b) {
return a * b; // Только возвращает результат
}

console.log(multiply(2, 5)); // Всегда 10

Чистой она называется, потому что она независима и даёт результат, строго выполняя свою задачу. Всегда проще представить себе обычные арифметические операции – отличный пример. Выше мы создали чистую функцию multiply(a, b), и в отличие от sum, она умножает a на b. Потом, можем вывести в консоль значение, равное результату функции multiply с аргументами 2 и 5.

  1. Метод – функция, которая является свойством объекта и работает с его данными.
const user = {
name: "Дарт Вейдер",
// Метод объекта
sayHi() {
console.log(`Привет, я ${this.name}!`);
}
};

user.sayHi(); // "Привет, я Дарт Вейдер!"

В примере выше у нас есть объект – user, константа, у которой есть свойство name, равное какому-то значению, и самое главное – внутри объекта user есть функция sayHi().

Она находится внутри объекта, следовательно, просто так к ней не обратиться – она зависима и принадлежит объекту user. Поэтому, вызывая эту функцию sayHi, мы должны указать сначала путь к этой функции – название объекта-владельца. Владелец и свойство/функция разделяются точкой – в нашем случае, это user.sayHi().

Скобки всегда указываются в функциях и методах, даже если аргументов нет. В роли аргументов могут выступать явно указанные данные, переменные/константы:

function greet(name) {
console.log(`Привет, ${name}!`);
}

greet("Йода"); // "Привет, Йода!"
greet("Падме"); // "Привет, Падме!"

После того, как функция выполнит свою работу, она должна что-то вернуть, какой-то результат. «return» возвращает результат (если его нет, функция вернёт неопределённое значение – undefined):

function checkAge(age) {
if (age >= 18) {
return "Доступ разрешён";
} else {
return "Доступ запрещён";
}
}

console.log(checkAge(20)); // "Доступ разрешён"

Имя функции должно быть глаголом (сделать, получить) и на английском языке – getData, calculateSum. Логика не должна смешиваться – одна функция-одна задача. И чистые функции, конечно, предпочтительнее.


Каррирование

Например, есть такое понятие как каррирование — это процесс преобразования функции от нескольких аргументов в последовательность функций, каждая из которых принимает по одному аргументу.

Вместо того, чтобвы вызывать функцию так:

add(1, 2, 3)

Мы сможем вызывать так:

add(1)(2)(3)

И в дальнейшем это позволит нам «замораживать» аргументы, без необходимости передавать все три. Пример:

let addOne = add(1);
let addOneAndTwo = addOne(2);
let result = addOneAndTwo(3);

Шаблон таков - у нас есть функция:

function add(a, b, c) {
return a + b + c;
}

Мы создаём функцию curry, которая принимает исходную функцию и возвращает её каррированную версию:

function curry(fn) {
return function curried(...args) {
// Если передали достаточно аргументов — вызываем исходную функцию
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
// Иначе возвращаем функцию, которая ждёт остальные аргументы
return function (...nextArgs) {
return curried.apply(this, args.concat(nextArgs));
}
}
};
}

Пояснение. fn.length это значение количества параметров у исходной функции (например, add(a,b,c) соответственно имеет fn.length === 3, т.е. три параметра). В …args мы собираем все переданные аргументы, и если их хватает - вызываем fn. Если не хватает, то возвращаем новую функцию, которая запомнит текущие фрагменты и дождётся остальных.

Пример, который мы привели выше (curry(fn)) это непростая функция, но более универсальная, способная работать с любым количеством аргументов. Можно сделать и более простой шаблон:

function curry3(f) {
return function(a) {
return function(b) {
return function(c) {
return f(a, b, c);
};
};
};
}

// Использование:
const curriedAdd = curry3(add);
curriedAdd(1)(2)(3); // → 6

Это не так универсально, но может быть нагляднее - здесь три аргумента. Есть и более современные способы, к примеру, при совмещении стрелочных функций и каррирования:

const curry = (fn) => (...args) =>
args.length >= fn.length
? fn(...args)
: (...nextArgs) => curry(fn)(...args, ...nextArgs);

Работает абсолютно так же, просто сделано в другом стиле.

Каррирование позволяет частично применять фунции, создавать специализированные функции без дублирования кода. Нужно взять функцию, обернуть её (как мы сделали в let addOne, к примеру), и затем вызывать.


Функции как объекты первого класса

В JavaScript функции являются полноценными объектами. Это означает, что функции можно присваивать переменным, передавать как аргументы в другие функции и возвращать из функций как результат.

Функции можно хранить в переменных:

// Обычная функция
function sayHello() {
console.log("Привет!");
}

// Присваиваем функцию переменной
const greet = sayHello;
greet(); // "Привет!"

Функции можно передавать как аргументы:

function execute(func) {
func();
}

function showMessage() {
console.log("Сообщение выполнено");
}

execute(showMessage); // "Сообщение выполнено"

Функции можно возвращать из других функций:

function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}

const double = createMultiplier(2);
console.log(double(5)); // 10

const triple = createMultiplier(3);
console.log(triple(5)); // 15

Функции можно хранить в массивах и объектах:

// Массив функций
const operations = [
function(a, b) { return a + b; },
function(a, b) { return a - b; },
function(a, b) { return a * b; }
];

console.log(operations[0](10, 5)); // 15
console.log(operations[1](10, 5)); // 5
console.log(operations[2](10, 5)); // 50

// Объект с функциями
const calculator = {
add: function(a, b) { return a + b; },
subtract: function(a, b) { return a - b; },
multiply: function(a, b) { return a * b; }
};

console.log(calculator.add(10, 5)); // 15
console.log(calculator.subtract(10, 5)); // 5
console.log(calculator.multiply(10, 5)); // 50

Функции могут иметь собственные свойства:

function counter() {
counter.count++;
return counter.count;
}

counter.count = 0;

console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

Неявный return

Стрелочные функции в JavaScript поддерживают неявный возврат значения. Когда тело стрелочной функции состоит из одного выражения без фигурных скобок, результат этого выражения автоматически возвращается.

Простой пример неявного возврата:

// С фигурными скобками и явным return
const sum1 = (a, b) => {
return a + b;
};

// Без фигурных скобок - неявный return
const sum2 = (a, b) => a + b;

console.log(sum1(2, 3)); // 5
console.log(sum2(2, 3)); // 5

Неявный возврат особенно удобен для коротких функций:

// Умножение
const multiply = (a, b) => a * b;

// Возведение в квадрат
const square = x => x * x;

// Проверка чётности
const isEven = num => num % 2 === 0;

// Получение длины строки
const getLength = str => str.length;

console.log(multiply(4, 5)); // 20
console.log(square(5)); // 25
console.log(isEven(10)); // true
console.log(getLength("JavaScript")); // 10

При работе с объектами нужно использовать круглые скобки:

// Создание объекта с неявным возвратом
const createUser = (name, age) => ({ name, age });

const user = createUser("Анакин", 25);
console.log(user); // { name: "Анакин", age: 25 }

Неявный возврат упрощает работу с методами массивов:

const numbers = [1, 2, 3, 4, 5];

// map с неявным возвратом
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// filter с неявным возвратом
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4]

// find с неявным возвратом
const firstEven = numbers.find(num => num % 2 === 0);
console.log(firstEven); // 2

Контекст this в стрелочных функциях

Стрелочные функции ведут себя иначе, чем обычные функции, когда дело доходит до ключевого слова this. Стрелочные функции не создают собственный контекст выполнения и заимствуют this из внешней области видимости.

Обычные функции создают свой контекст this:

const person = {
name: "Люк Скайуокер",
regularMethod: function() {
console.log(this.name); // "this" указывает на объект person
},
arrowMethod: () => {
console.log(this.name); // "this" указывает на глобальный объект
}
};

person.regularMethod(); // "Люк Скайуокер"
person.arrowMethod(); // undefined (или ошибка в строгом режиме)

Стрелочные функции сохраняют контекст из внешней области:

const jedi = {
name: "Оби-Ван Кеноби",
sayName: function() {
// Обычная функция создает свой контекст
setTimeout(function() {
console.log(this.name); // undefined (this указывает на window)
}, 1000);

// Стрелочная функция сохраняет контекст
setTimeout(() => {
console.log(this.name); // "Оби-Ван Кеноби"
}, 1000);
}
};

jedi.sayName();

Пример с обработчиками событий:

const button = {
label: "Кнопка",
clickHandler: function() {
// Стрелочная функция сохраняет контекст button
document.getElementById("myButton").addEventListener("click", () => {
console.log(this.label); // "Кнопка"
});
}
};

button.clickHandler();

Стрелочные функции полезны в методах объектов, которые возвращают функции:

const spaceship = {
name: "Тысячелетний сокол",
getPilot: function() {
return () => {
return this.name; // Сохраняет контекст spaceship
};
}
};

const pilot = spaceship.getPilot();
console.log(pilot()); // "Тысячелетний сокол"

Пример с цепочкой вызовов:

const calculator = {
value: 0,
add: function(num) {
this.value += num;
return this;
},
multiply: function(num) {
this.value *= num;
return this;
},
getResult: () => {
return this.value; // this указывает не на calculator
}
};

// Работает с обычными функциями
const result = calculator.add(5).multiply(3);
console.log(result.value); // 15

// Не работает со стрелочной функцией
console.log(calculator.getResult()); // undefined

Стрелочные функции не подходят для конструкторов:

// Обычная функция как конструктор
function Jedi(name) {
this.name = name;
this.sayName = function() {
console.log(this.name);
};
}

const luke = new Jedi("Люк");
luke.sayName(); // "Люк"

// Стрелочная функция не может быть конструктором
const Sith = (name) => {
this.name = name; // this указывает не туда
};

// Это вызовет ошибку
// const vader = new Sith("Вейдер");

Ключевое различие:

  • Обычные функции создают свой контекст this при вызове
  • Стрелочные функции заимствуют this из места своего создания
  • Стрелочные функции не имеют arguments, super и new.target
  • Стрелочные функции всегда анонимны и не могут быть конструкторами

Когда нужен динамический контекст this, используют обычные функции. Когда контекст должен сохраняться, выбирают стрелочные функции.


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).