Функції в JavaScript: чи все ви про них знаєте
У цій статті я надам зрозуміле уявлення про функції в контексті мови програмування 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 виконує прохід кодом двічі. У момент першого проходу він реєструє в памʼяті імена змінних, зокрема тих, яким було присвоєні функції, але не реєструє їхні значення*.
Під час другого проходу він присвоює значення цим змінним і викликає їх. Але функції-декларації реєструються повністю, разом з тілом функції, таким чином під час другого проходу вони вже є в памʼяті. І саме за рахунок цього ми можемо викликати такі функції до їхнього оголошення.
* Окрім змінних, оголошених за допомогою ключового слова 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, обробка даних, що надходять у міру їхнього отримання. Наприклад стримінговий відеосервіс, де отримати та відобразити великий відеофайл одразу неможливо — тому ми його отримуємо частинами і підвантажуємо наступну частину тільки за потреби.
Сподіваюся, ви дізналися щось нове та у вас зʼявилося чітке розуміння різниці між видами функцій.
15 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів