Функції

Загалом кажучи, функція – це "підпрограма", що може бути викликана кодом, зовнішнім (або внутрішнім – у випадку рекурсії) щодо самої функції. Подібно до самої програми, функція складається з послідовності інструкцій, що називається тілом функції. Значення можуть бути передані функції як параметри, і функція повертає значення.

У JavaScript функції є об'єктами першого класу, тому що їх можна передавати в інші функції, повертати з функцій, присвоювати змінним і властивостям. Також вони можуть мати властивості та методи, як і будь-який інший об'єкт. Від інших об'єктів їх відрізняє те, що функцію можна викликати.

Більше прикладів і пояснень дивіться в Посібнику JavaScript про функції.

Опис

Значення функцій зазвичай є примірниками Function. Дивіться інформацію про властивості та методи об'єктів Function на сторінці Function. Викличні значення змушують typeof повертати "function", а не "object".

[!NOTE] Не всі викличні значення є примірниками Function. Наприклад, об'єкт Function.prototype є викличним, але не є примірником Function. Також можна вручну задати ланцюжок прототипів функції так, щоб вона більше не успадковувала властивості Function.prototype. Однак такі випадки дуже рідкісні.

Повернене значення

Усталено, якщо виконання функції не закінчується інструкцією return, або якщо після ключового слова return немає виразу, то поверненим значенням стає undefined. Інструкція return дає змогу повернути з функції довільне значення. Один виклик функції може повернути лише одне значення, але можна імітувати ефект повернення декількох значень, повернувши об'єкт або масив і деструктурувавши результат.

[!NOTE] Конструктори, викликані з new, використовують для визначення поверненого значення іншу логіку.

Передача аргументів

Параметри та аргументи мають дещо різні значення, але у ВебДоках ми нерідко використовуємо ці терміни як взаємозамінні. Як стислий приклад:

function formatNumber(num) {
  return num.toFixed(2);
}

formatNumber(2);

У цьому прикладі змінна num зветься параметром функції: вона оголошена оточеному дужками списку визначення функції. Функція очікує того, що параметр num буде числом – хоча це не можна забезпечити в JavaScript без написання коду валідації при виконанні. У виклику formatNumber(2) число 2 є аргументом функції: це значення, що фактично передається функції у виклику функції. Значення аргументу можна отримати всередині тіла функції через відповідне ім'я параметра або об'єкт arguments.

Аргументи завжди передаються за значенням і ніколи не передаються за посиланням. Це означає, що якщо функція повторно присвоює щось параметру, то значення поза функцією не змінюється. Якщо точніше, то аргументи-об'єкти передаються за співвикористанням, тобто якщо змінюються властивості об'єкта, то такі зміни впливають на цей об'єкт поза функцією. Наприклад:

function updateBrand(obj) {
  // Внесення змін до об'єкта спостерігається поза функцією
  obj.brand = "Toyota";
  // Спроба переприсвоїти параметр, але це не вплине
  // на значення змінної поза функцією
  obj = null;
}

const car = {
  brand: "Honda",
  model: "Accord",
  year: 1998,
};

console.log(car.brand); // Honda

// Передача об'єктного посилання до функції
updateBrand(car);

// updateBrand вносить зміни до car
console.log(car.brand); // Toyota

Ключове слово this посилається на об'єкт, на якому відбувається звертання до функції – воно не вказує на наразі виконувану функцію, тож необхідно звертатися до значення функції за назвою, навіть всередині її тіла.

Визначення функцій

Грубо кажучи, JavaScript має чотири різновиди функцій:

  • Звичайна функція – може повернути що завгодно; бувши закликана, завжди виконується до кінця
  • Генераторна функція – повертає об'єкт Generator; може бути призупинена та відновлена оператором yield
  • Асинхронна функція – повертає Promise; може бути призупинена та відновлена оператором await
  • Асинхронна генераторна функція – повертає об'єкт AsyncGenerator; можна використовувати як оператор await, так і оператор yield

Для кожного різновиду функції є кілька способів визначення:

Оголошення

function, function*, async function, async function*

Вираз

function, function*, async function, async function*

Конструктор

Function(), GeneratorFunction(), AsyncFunction(), AsyncGeneratorFunction()

На додачу до цього, є особливий синтаксис для визначення стрілкових функцій та методів, які надають більш точну семантику для їх використання. Класи концептуально не є функціями (тому, що вони викидають помилку при виклику без new), але також успадковують від Function.prototype та мають typeof MyClass === "function".

// Конструктор
const multiply = new Function("x", "y", "return x * y");

// Оголошення
function multiply(x, y) {
  return x * y;
} // Немає потреби ставити тут крапку з комою

// Вираз; ця функція є анонімною, але присвоюється змінній
const multiply = function (x, y) {
  return x * y;
};
// Вираз; ця функція має власну назву
const multiply = function funcName(x, y) {
  return x * y;
};

// Стрілкова функція
const multiply = (x, y) => x * y;

// Метод
const obj = {
  multiply(x, y) {
    return x * y;
  },
};

Кожен з цих варіантів запису робить приблизно одне й те ж, але є деякі відмінності у логіці.

  • Записи конструктора Function(), виразу function і оголошення function створюють повноцінні об'єкти функцій, які можна конструювати за допомогою new. Натомість стрілкові функції та методи не можна конструювати. Асинхронні функції, генераторні функції та асинхронні генераторні функції не можна конструювати, незалежно від запису.
  • Оголошення function створює функції, які піднімаються. Решта записів не піднімає функцію, і значення функції тоді видно лише після визначення.
  • Стрілкові функції та конструктор Function() завжди створюють анонімні функції, тобто їх не можна легко викликати рекурсивно. Один зі способів викликати стрілкову функцію рекурсивно – присвоїти її змінній.
  • Запис стрілкових функцій не має доступу до arguments і this.
  • Конструктор Function() не може звертатися до жодних локальних змінних – він має доступ лише до глобальної області видимості.
  • Конструктор Function() призводить до компіляції при виконанні та нерідко є повільнішим за решту записів.

Що до виразів function, то є різниця між назвою функції та змінною, якій ця функція присвоєна. Назва функції не може бути змінена, а змінна, якій функція присвоєна, може бути присвоєна повторно. Назва функції може відрізнятися від змінної, якій функція присвоєна: вони не мають взаємозв'язку. Назва функції може вживатися лише всередині тіла функції. Спроби використати його поза тілом функції призведе до помилки (або до отримання іншого значення, якщо та сама назва оголошена ще десь). Наприклад:

const y = function x() {};
console.log(x); // ReferenceError: x is not defined

З іншого боку, змінна, якій присвоєна функція, обмежується лише своєю областю видимості, яка гарантовано включає область видимості, у якій функція оголошена.

Оголошення функції також створює змінну з такою ж назвою, як і назва функції. Таким чином, на відміну від функцій, визначених виразами функції, до функцій, визначених оголошеннями функції, можна звертатися за їхньою назвою в межах області видимості, в якій вони визначені, а також всередині їхнього власного тіла.

Функції, визначеній new Function, вихідний код збирається динамічно, що можна помітити, якщо її серіалізувати. Наприклад, console.log(new Function().toString()) дає:

function anonymous(
) {

}

Це реальний вихідний код, що вживається для компіляції функції. Проте попри те, що конструктор Function() створює функцію з назвою anonymous, ця назва не додається до області видимості тіла. Тіло має доступ лише до глобальних змінних. Наприклад, наступний код призведе до помилки:

new Function("alert(anonymous);")();

Функція, визначена виразом функції або оголошенням функції, успадковує поточну область видимості. Тобто така функція утворює замикання. З іншого боку, функція, визначена конструктором Function, не успадковує жодної області видимості, крім глобальної (яку успадковують усі функції).

// p – глобальна змінна
globalThis.p = 5;
function myFunc() {
  // p – локальна змінна
  const p = 9;

  function decl() {
    console.log(p);
  }
  const expr = function () {
    console.log(p);
  };
  const cons = new Function("\tconsole.log(p);");

  decl();
  expr();
  cons();
}
myFunc();

// Виводить:
// 9 (для 'decl' з оголошення функції (поточна область видимості))
// 9 (для 'expr' з виразу функції (поточна область видимості))
// 5 (для 'cons' з конструктора Function (глобальна область видимості))

Функції, визначені виразами функції та оголошеннями функції, розбираються лише раз, а функція, визначена конструктором Function, розбирає рядок, переданий їй, кожного разу, коли конструктор викликається. Хоча вираз функції створює замикання кожного разу, тіло не розбирається заново, тож вирази функцій все ж швидші за new Function(...). Таким чином, конструктора Function слід уникати, коли це можливо.

Оголошення функції може ненавмисно перетворитися на вираз функції, коли воно зустрічається в контексті виразу.

// Оголошення функції
function foo() {
  console.log("АГОВ!");
}

doSomething(
  // Вираз функції, переданий як аргумент
  function foo() {
    console.log("АГОВ!");
  },
);

З іншого боку, вираз функції також може перетворитися на оголошення функції. Інструкція-вираз не може починатися з ключових слів function або async function, що є поширеною помилкою при реалізації IIFE (негайно закликаних виразів функцій).

function () { // SyntaxError: Function statements require a function name
  console.log("АГОВ!");
}();

function foo() {
  console.log("АГОВ!");
}(); // SyntaxError: Unexpected token ')'

Замість цього, слід розпочати інструкцію-вираз чимось іншим, щоб ключове слово function однозначно відкривало вираз функції. Поширеними варіантами є групування та використання void.

(function () {
  console.log("АГОВ!");
})();

void function () {
  console.log("АГОВ!");
}();

Параметри функції

Кожний параметр функції є простим ідентифікатором, до якого можна звернутися в локальній області видимості.

function myFunc(a, b, c) {
  // Тут можна звертатися до значень a, b та c
}

Є три особливі синтаксиси параметрів:

  • Усталені параметри дають формальним параметрам змогу ініціалізуватися усталеними значеннями, якщо не передано жодного значення або передано undefined.
  • Решта параметрів дає змогу представляти нескінченну кількість аргументів у вигляді масиву.
  • Деструктурування дає змогу розпаковувати елементи з масивів або властивості з об'єктів у окремі змінні.
function myFunc({ a, b }, c = 1, ...rest) {
  // Тут можна звертатися до значень a, b, c та rest
}

Є певні наслідки, якщо використовується один зі синтаксисів непростих параметрів, наведених вище:

  • Не можна застосовувати до тіла функції "use strict": це призводить до синтаксичної помилки.
  • Навіть якщо функція не перебуває в суворому режимі, застосовуються певні особливості функцій у суворому режимі, як-от об'єкт arguments перестає синхронізуватися з іменованими параметрами, а властивість arguments.callee викидає помилку, якщо спробувати до неї звернутися, а також заборонене дублювання імен параметрів.

Об'єкт arguments

До аргументів функції всередині неї можна звернутися за допомогою об'єкта arguments.

arguments

Масивоподібний об'єкт, що вміщає аргументи, передані до функції, що наразі виконується.

arguments.callee

Функція, що наразі виконується.

arguments.length

Число аргументів, переданих до функції.

Функції-гетери та функції-сетери

На будь-якому стандартному вбудованому або визначеному користувачем об'єкті, що підтримує додавання нових властивостей, можна визначити аксесорні властивості. У літералах об'єктів та класах можна використовувати спеціальні синтаксиси для визначення гетера та сетера аксесорної властивості.

get

Зв'язує властивість об'єкта з функцією, що буде викликана, коли до цієї властивості відбудеться звертання.

set

Зв'язує властивість об'єкта з функцією, що буде викликана, коли відбудеться спроба присвоєння цієї властивості.

Зверніть увагу, що такий синтаксис створює властивість об'єкта, а не метод. Функцію-гетер або функцію-сетер можна отримати лише за допомогою API рефлексії, наприклад, Object.getOwnPropertyDescriptor().

Функції блокового рівня

У суворому режимі функція всередині блоку обмежена областю видимості цього блоку. До ES2015 функції блокового рівня були заборонені в суворому режимі.

"use strict";

function f() {
  return 1;
}

{
  function f() {
    return 2;
  }
}

f() === 1; // true

// f() === 2 у несуворому режимі

Функції блокового рівня в несуворому коді

Якщо коротко: Не робіть цього.

У несуворому коді оголошення функцій всередині блоків поводяться дивно. Наприклад:

if (shouldDefineZero) {
  function zero() {
    // НЕБЕЗПЕКА: ризик проблем сумісності
    console.log("Це нуль.");
  }
}

Семантика цього в суворому режимі описана як слід: zero існує лише в межах області видимості блоку if. Якщо shouldDefineZero – хиба, то функція zero повинна ніколи не бути визначена, оскільки цей блок ніколи не виконується. Проте історично це було залишено невизначеним, тож різні браузери реалізували це в несуворому режимі по-різному. Більше про це – дивіться довідку оголошення function.

Безпечніший спосіб умовно визначати функції – присвоювати вирази функції змінним:

// Завдяки використанню var ця змінна доступна як глобальна,
// що наближає логіку до оголошення функції вищого рівня
var zero;
if (shouldDefineZero) {
  zero = function () {
    console.log("Це нуль.");
  };
}

Приклади

Повернення відформатованого числа

Наступна функція повертає рядок, що містить відформатоване представлення числа, вирівняне зліва нулями.

// Ця функція повертає рядок, вирівняний зліва нулями
function padZeros(num, totalLen) {
  let numStr = num.toString(); // Ініціалізувати повернене значення як рядок
  const numZeros = totalLen - numStr.length; // Обчислити число нулів
  for (let i = 1; i <= numZeros; i++) {
    numStr = `0${numStr}`;
  }
  return numStr;
}

Наступні інструкції викликають функцію padZeros.

let result;
result = padZeros(42, 4); // повертає "0042"
result = padZeros(42, 2); // повертає "42"
result = padZeros(5, 4); // повертає "0005"

З'ясування, чи існує функція

Чи існує функція, можна з'ясувати за допомогою оператора typeof. У наступному прикладі виконується перевірка, щоб з'ясувати, чи об'єкт window має властивість noFunc, яка є функцією. Якщо так, вона використовується; інакше виконується якась інша дія.

if (typeof window.noFunc === "function") {
  // використати noFunc()
} else {
  // якась інша дія
}

Зверніть увагу, що в перевірці if використовується звертання до noFunc: після назви функції немає дужок (), тож сама функція не викликається.

Специфікації

Сумісність із браузерами

desktop mobile server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
functions
Chrome Full support 1
Edge Full support 12
Firefox Full support 1
Internet Explorer Full support 3
Opera Full support 3
Safari Full support 1
WebView Android Full support 1
Chrome Android Full support 18
Firefox for Android Full support 4
Opera Android Full support 10.1
Safari on iOS Full support 1
Samsung Internet Full support 1.0
Deno Full support 1.0
Node.js Full support 0.10.0
arguments Chrome Full support 1
Edge Full support 12
Firefox Full support 1
Internet Explorer Full support 3
Opera Full support 3
Safari Full support 1
WebView Android Full support 1
Chrome Android Full support 18
Firefox for Android Full support 4
Opera Android Full support 10.1
Safari on iOS Full support 1
Samsung Internet Full support 1.0
Deno Full support 1.0
Node.js Full support 0.10.0
arguments.@@iterator
Chrome Full support 52
Edge Full support 12
Firefox Full support 46
Internet Explorer No support Ні
Opera Full support 39
Safari Full support 9
WebView Android Full support 52
Chrome Android Full support 52
Firefox for Android Full support 46
Opera Android Full support 41
Safari on iOS Full support 9
Samsung Internet Full support 6.0
Deno Full support 1.0
Node.js Full support 4.0.0
arguments.callee
Chrome Full support 1
Edge Full support 12
Firefox Full support 1
Internet Explorer Full support 6
Opera Full support 4
Safari Full support 1
WebView Android Full support 1
Chrome Android Full support 18
Firefox for Android Full support 4
Opera Android Full support 10.1
Safari on iOS Full support 1
Samsung Internet Full support 1.0
Deno Full support 1.0
Node.js Full support 0.10.0
arguments.length
Chrome Full support 1
Edge Full support 12
Firefox Full support 1
Internet Explorer Full support 4
Opera Full support 4
Safari Full support 1
WebView Android Full support 1
Chrome Android Full support 18
Firefox for Android Full support 4
Opera Android Full support 10.1
Safari on iOS Full support 1
Samsung Internet Full support 1.0
Deno Full support 1.0
Node.js Full support 0.10.0
Arrow functions Chrome Full support 45
Edge Full support 12
Firefox Full support 22
footnote
Internet Explorer No support Ні
Opera Full support 32
Safari Full support 10
WebView Android Full support 45
Chrome Android Full support 45
Firefox for Android Full support 22
footnote
Opera Android Full support 32
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 4.0.0
Trailing comma in parameters
Chrome Full support 58
Edge Full support 12
Firefox Full support 52
Internet Explorer No support Ні
Opera Full support 45
Safari Full support 10
WebView Android Full support 58
Chrome Android Full support 58
Firefox for Android Full support 52
Opera Android Full support 43
Safari on iOS Full support 10
Samsung Internet Full support 7.0
Deno Full support 1.0
Node.js Full support 8.0.0
Block-level functions
Chrome Full support 49
Edge Full support 12
Firefox Full support 46
Internet Explorer Full support 11
Opera Full support 36
Safari Full support 10
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 46
Opera Android Full support 36
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 4.0.0
Default parameters Chrome Full support 49
Edge Full support 14
Firefox Full support 15
Internet Explorer No support Ні
Opera Full support 36
Safari Full support 10
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 15
Opera Android Full support 36
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Destructured parameter with default value assignment
Chrome Full support 49
Edge Full support 14
Firefox Full support 41
Internet Explorer No support Ні
Opera Full support 36
Safari Full support 10
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 41
Opera Android Full support 36
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Parameters without defaults after default parameters
Chrome Full support 49
Edge Full support 14
Firefox Full support 26
Internet Explorer No support Ні
Opera Full support 36
Safari Full support 10
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 26
Opera Android Full support 36
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
get Chrome Full support 1
Edge Full support 12
Firefox Full support 1.5
Internet Explorer Full support 9
Opera Full support 9.5
Safari Full support 3
WebView Android Full support 1
Chrome Android Full support 18
Firefox for Android Full support 4
Opera Android Full support 14
Safari on iOS Full support 1
Samsung Internet Full support 1.0
Deno Full support 1.0
Node.js Full support 0.10.0
Computed property names
Chrome Full support 46
Edge Full support 12
Firefox Full support 34
Internet Explorer No support Ні
Opera Full support 47
Safari Full support 9.1
WebView Android Full support 46
Chrome Android Full support 46
Firefox for Android Full support 34
Opera Android Full support 33
Safari on iOS Full support 9.3
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 4.0.0
Method definitions Chrome Full support 39
Edge Full support 12
Firefox Full support 34
Internet Explorer No support Ні
Opera Full support 26
Safari Full support 9
WebView Android Full support 39
Chrome Android Full support 39
Firefox for Android Full support 34
Opera Android Full support 26
Safari on iOS Full support 9
Samsung Internet Full support 4.0
Deno Full support 1.0
Node.js Full support 4.0.0
Async generator methods
Chrome Full support 63
Edge Full support 79
Firefox Full support 55
Internet Explorer No support Ні
Opera Full support 50
Safari Full support 12
WebView Android Full support 63
Chrome Android Full support 63
Firefox for Android Full support 55
Opera Android Full support 46
Safari on iOS Full support 12
Samsung Internet Full support 8.0
Deno Full support 1.0
Node.js Full support 10.0.0
Async methods
Chrome Full support 55
Edge Full support 15
Firefox Full support 52
Internet Explorer No support Ні
Opera Full support 42
Safari Full support 10.1
WebView Android Full support 55
Chrome Android Full support 55
Firefox for Android Full support 52
Opera Android Full support 42
Safari on iOS Full support 10.3
Samsung Internet Full support 6.0
Deno Full support 1.0
Node.js Full support 7.6.0
Generator methods are not constructable (ES2016)
Chrome Full support 42
Edge Full support 13
Firefox Full support 43
Internet Explorer No support Ні
Opera Full support 29
Safari Full support 9.1
WebView Android Full support 42
Chrome Android Full support 42
Firefox for Android Full support 43
Opera Android Full support 29
Safari on iOS Full support 9.3
Samsung Internet Full support 4.0
Deno Full support 1.0
Node.js Full support 6.0.0
Rest parameters Chrome Full support 47
Edge Full support 12
Firefox Full support 15
Internet Explorer No support Ні
Opera Full support 34
Safari Full support 10
WebView Android Full support 47
Chrome Android Full support 47
Firefox for Android Full support 15
Opera Android Full support 34
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
Destructuring rest parameters
Chrome Full support 49
Edge Full support 79
Firefox Full support 52
Internet Explorer No support Ні
Opera Full support 36
Safari Full support 10
WebView Android Full support 49
Chrome Android Full support 49
Firefox for Android Full support 52
Opera Android Full support 36
Safari on iOS Full support 10
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 6.0.0
set Chrome Full support 1
Edge Full support 12
Firefox Full support 1.5
Internet Explorer Full support 9
Opera Full support 9.5
Safari Full support 3
WebView Android Full support 1
Chrome Android Full support 18
Firefox for Android Full support 4
Opera Android Full support 14
Safari on iOS Full support 1
Samsung Internet Full support 1.0
Deno Full support 1.0
Node.js Full support 0.10.0
Computed property names
Chrome Full support 46
Edge Full support 12
Firefox Full support 34
Internet Explorer No support Ні
Opera Full support 47
Safari Full support 9.1
WebView Android Full support 46
Chrome Android Full support 46
Firefox for Android Full support 34
Opera Android Full support 33
Safari on iOS Full support 9.3
Samsung Internet Full support 5.0
Deno Full support 1.0
Node.js Full support 4.0.0

Дивіться також