Функції в JavaScript: чи все ви про них знаєте

Усі статті, обговорення, новини про Front-end — в одному місці. Підписуйтеся на телеграм-канал!

У цій статті я надам зрозуміле уявлення про функції в контексті мови програмування JavaScript. Розберемося з різними видами функцій, їхніми ролями та впливом на структуру програм, а також визначимо принципові відмінності між ними.

Отож пориньмо у світ функцій і розкриймо їхню роль у створенні ефективного та організованого коду.

Що таке функція

У JavaScript функція — це блок коду (підпрограма), який може бути визначений для виконання конкретної задачі. Функції є фундаментальним будівельним блоком JavaScript. Самі ж функції складаються з послідовності інструкцій (тіла функції). Будь-яка функція є обʼєктом і має свої властивості та методи.

Спочатку згадаємо з чого складається функція

(Цей блок прям для дошкільнят).

Імʼя функціі (Function name)

Функції можуть мати імена, які використовуються для їхнього виклику. Наприклад:

function sayHi() {
  console.log("Hello world!")
}

Параметри (Parameters)

Функції можуть приймати характеристики, які є значеннями, що передаються під час виклику функції. Параметри вказуються в дужках під час оголошення функції та використовуються всередині тіла функції. Наприклад:

function sayHi(name) {
  console.log(`Hello,  ${name}!`)
}

Тіло функції (Function Body)

Це саме і є той блок коду, який перебуває між фігурними дужками {}. Усередині тіла функції визначені операції, які будуть виконуватися під час її виклику. У прикладі вище тілом функції є console.log(`Hello, ${name}!`

Значення, що повертається (Return Value)

Функція може повертати значення за допомогою оператора return. Це значення передається назад у ділянку коду, який викликав функцію. Наприклад:

function double(x) {
  return x * 2;
}
const result = double(2)
console.log(result) // 4

Виклик функції (Function Invocation)

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

sayHi("John") // Hello, John!

Види функцій

Функції-декларації (function declaration)

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

Розберемо детальніше, що означає «підіймається вгору»?

Справа в тому, що інтерпретатор JavaScript виконує прохід кодом двічі. У момент першого проходу він реєструє в памʼяті імена змінних, зокрема тих, яким було присвоєні функції, але не реєструє їхні значення*.

Під час другого проходу він присвоює значення цbм змінним і викликає їх. Але функції-декларації реєструються повністю, разом з тілом функції, таким чином під час другого проходу вони вже є в памʼяті. І саме за рахунок цього ми можемо викликати такі функції до їхнього оголошення.


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


Уважно ознайомтесь з прикладом:

myFirstFunction() // Hello (Функція зареєстрована разом з тілом під час першого проходу, тому вона доступна до виклику)
mySecondFunction() // Error: Cannot access 'SecondFunction' before initialization (Під час першого проходу було зареєстрована тільки назва змінної, без будь якого значення)
myThirdFunction() // Error: myFunction is not a function (Під час першого проходу була зареєстрована змінна, якій було присвоєно значення undefined. undefined – is not a function)
function myFirstFunction() {
console.log("Hello")
}
const mySecondFunction = () => {
    console.log("Hello 2")
}
var myThirdFunction = function() {
    console.log("Hello 3")
}

Ця подія, яка відбувається під час першого проходу інтрепретатора кодом, реєстрація змінних і функцій називається hoisting (спливання). (Спливання вгору всіх імен змінних та функцій-декларацій).

Функції-вирази (Function Expression)

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

Функції-вирази можуть бути реалізовані двома способами:

  • за допомогою звичайних функцій;
  • за допомогою стрілочних функцій.

Звичайні функції

Так само за допомогою ключового слова function ми ініціалізуємо функцію в якості значення змінної:

const myFunction = function() {
    console.log("Hello")
}

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

Наведу один цікавий приклад:

sayHello("Kate") // Hello, Kate
helloJohn() // Error: Cannot access ' helloJohn ' before initialization

function sayHello(name) {
    console.log(`Hello, ${name}`)
}
const helloJohn = () => sayHello("John");

Стрілочні функції (Arrow Functions)

Стрілочні функції — це синтаксис введений в ECMAScript 6 для визначення функцій в JavaScript. Вони надають більш короткий та зручний спосіб створення функцій, особливо корисний для функцій з одним виразом.

const myFunction = () => {
    console.log("Hello")
}

Або такі короткі функції можна записувати в один ряд без фігурних дужок:

const myFunction = () => console.log("Hello")

Також якщо стрілочна функція повинна щось повертати, але не виконує якісь складні дії, якщо вона пишеться в один ряд, можна не використовувати ключове слово return:

const result = (x) => x * 2

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

Особливості стрілочних функцій

Контекст (this)

Поспілкувавшись на цю тему з деякими початківцями, я виявив, що не всі знають про відмінність контексту у функціях. А ті, хто знають, плутаються в термінах, вважаючи, що у функцій-декларацій і функцій-виразів this посилається на різні об’єкти.

Перш за все я хочу розділити функції на два види: стрілочні функції та всі інші функції, водночас опустивши терміни «декларації» та «вирази» взагалі.

Контекст у звичайних функціях

this буде залежати від того, де така функція буде викликатися. Абсолютно не має значення, чи це функція-вираз, чи функція-декларація. Головне, що мовиться про всі функції, окрім стрілочних.

Приклад:

function regularFunction(){
    console.log(this)
}
const obj = {
    method: regularFunction
}
obj.method() // obj (Контекстом є область де функція викликається)
regularFunction() // window (Функція викликається в глобальному обʼєкті)

Контекст стрілочних функцій

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

Приклад:

const arrowFunction = () => {
    console.log(this)
}
const obj = {
    method: arrowFunction
}
obj.method() // window
arrowFunction // window

В обох випадках контекст не буде змінено і залишиться таким, яким він був на момент оголошення функції.

Робота з аргументами функцій

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

Аргументи в звичайних функціях

У звичайних функціях завжди є доступ до масивоподібного обʼєкту arguments, який зберігає в себе всі аргументи, які ми передаємо в функцію.

const myFunction = function() {
    console.log(arguments)
}
// Викликаємо цю функцію в барузері:
myFunction(1, 2, 3) // [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ] 
// Викликаємо цю функцію в Node.js:
myFunction(1, 2, 3) // [Arguments] { '0': 1, '1': 2, '2': 3 } 

Не варто звертати увагу на різні результати виклику. Вони залежать від середовища, в якому викликаються.

Ми бачимо, що отримали всі аргументи в масивоподібному обʼєкті (псевдомасиві) в браузері, або в звичайному обʼєкті в Node.js, які ми передали в функцію. У браузері також отримуємо додаткові властивості, які в сучасних проєктах практично не використовуються, і зупинятися на них я не бачу сенсу.

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

const argsArray = [...arguments]; 

або

const argsArray = Array.from(arguments);

Аргументи в стрілочних функціях

Різниця полягає в тому, що стрілочні функції не мають свого власного об’єкта arguments. Для обробки непередбачуваної кількості аргументів у стрілочних функціях ми повинні використовувати деструктуризацію параметрів та оператор розпакування (spread operator), явно вказуваючи, що ми очікуємо аргументи в момент оголошення.

Приклад:

const myFunction = (... args) => {
    console.log(args)
}
myFunction(1,2,3) // [1, 2, 3] (В цьому випадку ми вже отримуємо справжній масив)

Функція зворотнього виклику (Callback)

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

const callback = () => console.log("Hello");
function myFunction(callback) {
//…some code
  callback(); // Функція виконається після того, як виконається інший (попередній) код всередені основної функції. При цьому виклик колбеку не зупинить виконання іншого коду
//…some code
}
myFunction(callback); // Hello

Анонімні функції

Це функції, які оголошуються без імені. Вони використовуються для спрощення коду та поліпшення читабельності. Але не варто їх використовувати всюди, бо це призведе до зворотного результату.

Приклад використання:

const button = document.querySelector('.button');
button.addEventListener('click', function() {
  // ... some code ...
});

Асинхронні функції

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

Можуть бути застосовані в таких випадках:

  • завантаження даних з мережі;
  • обробка файлів;
  • виконання складних обчислень.

Асинхронні функції, якими ви вже могли користуватись:

  • setTimeout;
  • setInterval;
  • XMLHttpRequest;
  • fetch;
  • async/await.

Невеличкий приклад:

// за допомогою ключового слова async позначаємо, що функція буде асинхронною.
async function getData() {
 const response = await fetch("https://api.example.com/data");
 console.log(response.data);
}

Тільки коли ми отримаємо результат, і він запишеться в змінну, відпрацює console.log(response.data);.

async/await ми називаємо асинхронною функцією, але фактично вона навпаки синхронізує послідовність асинхронних операцій, роблячи код схожим на синхронний. (Як я і зазначив, ця тема потребує окремої статті).

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

Функції-генератори — це спеціальні типи функцій в JavaScript, які дозволяють генерувати послідовність значень. Я вважаю, що вони також потребують окремої статті, тому що прикладів їх використання і потреб, які вони можуть закривати, дуже багато.

Їхні особливості:

  • використовують ключове слово function* (з зірочкою);
  • Можуть містити оператор yield;
  • yield використовується для паузи виконання функції та повернення значення.

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

Простий приклад:

function* generateNumbers() {
  yield 1;
  yield 2;
  yield 3;
}
const generator = generateNumbers();
console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3

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


Сподіваюся, ви дізналися щось нове та у вас зʼявилося чітке розуміння різниці між видами функцій.

👍ПодобаєтьсяСподобалось17
До обраногоВ обраному8
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}

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

Власне кажучи, дуже класний матеріял!

краще статті про архітектуру та паттерни. чим 100 разів про елементарне та те саме.

Дуже крутий матеріал за легкістю розуміння, переслав своїм друзям-вайтішникам

Я б почитав від вас продовження про асинхронні та генератори

Не вказані функції-конструктори з вкладеними методами (старий стиль, да, але іноді важливий).

Здається, цей код не працюватиме навіть якщо змінити блоки місцями

function sayHello(name) {
    console.log(`Hello, ${name}`)
}
const helloJohn = sayHello('John')
sayHello("Kate") // Hello, Kate
helloJohn() // Uncaught TypeError TypeError: helloJohn is not a function

Щоб працювало, потрібно константі helloJohn присвоїти саму функцію, а не виклик функції з параметром ‘John’ і параметр передати вже при виклику

function sayHello(name) {
    console.log(`Hello, ${name}`)
}
const helloJohn = sayHello
sayHello("Kate") // Hello, Kate
helloJohn('John')

Ну або так має виглядати

function sayHello(name) {
    console.log(`Hello, ${name}`)
}
const helloJohn = () => { sayHello('John') }
sayHello("Kate") // Hello, Kate
helloJohn()

А функції-конструктори?

Багато бібліотек використвують tag function або як їх правильно назв — tag template
Вважаю дуже корисно не тільки використовувати але і розуміти як це працює.

Приклади graphql-tag
www.npmjs.com/package/graphql-tag

Styled Components
styled-components.com

Docs:
developer.mozilla.org/...​literals#tagged_templates

Особливості стрілочних функцій

здається там нема про prototype & new

const data = await fetch("api.example.com/data");
console.log(data);

мабуть тоді response, а не data

дякую, це прекрасно! коли думаєш, що знаєш по цих питаннях все, але ніт))

Підписатись на коментарі