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

Функции в JavaScript

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

Функции в JavaScript

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

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

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

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

Разбор:

  • Это базовый шаблон объявления функции: сначала имя и параметры, затем логика в теле.
  • Ключевое слово function сообщает движку, что ниже создаётся вызываемый блок кода.
  • Параметры в скобках описывают входные данные, которые функция получит во время вызова.
  • Тело в {} не выполняется автоматически; оно запускается только при вызове функции.
  • return завершает выполнение и отдаёт значение обратно в место, где функцию вызвали.

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

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

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

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

Разбор:

  • sum(a, b) принимает два числа и изолирует одну ответственность: операцию сложения.
  • const result = a + b делает промежуточный результат явным, что упрощает чтение и отладку.
  • const показывает, что переменная result не должна переопределяться в пределах блока.
  • 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

Разбор:

  • sum(2, 3) — вызов функции: аргументы сопоставляются параметрам a и b.
  • Результат вызова присваивается переменной total, чтобы использовать его дальше.
  • console.log(total) печатает уже вычисленное значение, не пересчитывая выражение.
  • Такой приём делает поток данных прозрачным: от входа к вычислению и затем к выводу.
  • Комментарий // 5 фиксирует ожидаемый результат и помогает новичку проверить себя.

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

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

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

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

Разбор:

  • greet() объявлена без параметров: её поведение не зависит от входных значений.
  • Внутри один побочный эффект — вывод строки через console.log.
  • Каждый вызов greet(); запускает одно и то же тело заново.
  • На примере видно главную силу функций: можно многократно использовать одну реализацию.
  • Даже когда аргументов нет, скобки обязательны, потому что они обозначают вызов.

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

Общая база: функции в коде — вызов, параметры, возврат и стек. Ниже — синтаксис JavaScript и интерактивное демо.

Play ITЗагрузка интерактивного демо…

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


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

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

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

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

Function Declaration Statement

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

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

Разбор:

  • Это учебная схема Function Declaration, которая показывает стандартную структуру синтаксиса.
  • <Параметры> описывают, какие данные функция ожидает на входе.
  • <тело> — набор операций, проверок и вычислений, выполняемых внутри функции.
  • 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)

Разбор:

  • Здесь функция создаётся как выражение и сохраняется в переменную sum.
  • function(a, b) { ... } может быть анонимной, потому что имя функции не обязательно.
  • return a + b возвращает результат, как и в обычном объявлении функции.
  • let x = sum(2, 2) демонстрирует, что переменная с функцией вызывается так же, как именованная функция.
  • Формат function expression удобен для колбэков и передачи функций как данных.

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

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

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

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

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

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

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

Разбор:

  • Это краткий синтаксис стрелочной функции, где => отделяет параметры от тела.
  • Такая функция является выражением и обычно присваивается переменной.
  • Если тело — одно выражение, значение возвращается неявно без return.
  • Стрелки часто используют для коротких колбэков в map, filter, find и обработчиках событий.
  • Важно помнить: стрелочная функция наследует this из внешнего контекста.

Они позволяют сокращать синтаксис функций, заменяя "{ 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

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

Код ITЗагрузка примера кода…

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

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

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

Код ITЗагрузка примера кода…

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

// Создание объекта с неявным возвратом
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 (или ошибка в строгом режиме)

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

Код ITЗагрузка примера кода…

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

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()); // "Тысячелетний сокол"

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

Код ITЗагрузка примера кода…

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

Код ITЗагрузка примера кода…

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

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

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