Функції та їх види JS

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

Функція складається з наступних частин:

  • function — це ключове слово, яке дає зрозуміти комп’ютеру, що далі буде оголошення функція.
  • Ім’я функції: Функція має унікальне ім’я (може бути анонімна), яке ідентифікує її в програмі. Існують певні правила іменування функцій.
  • Ім’я повинне бути записане в camelCase нотації

Виключення з правила становлять функції конструктори. Імена таких функцій пишуться використовуючи PascalCase

  • Оскільки кожна функція виконує певну дію, її ім’я має відображати цю дію. Використовуйте інформативні іменники з дієсловами в ролі префіксу. Наприклад getUserInfo,calculateFee,openPopup.
  • Конвенція найменування методів, які повертають логічне значення, полягає в тому, щоб перед присудком утворювати дієслова, такі як ‘is‘ або ‘has‘, як питання, або використовувати присудок як твердження. Наприклад, для перевірки активності користувача ви скористаєтеся isUserActive(), або для перевірки існування користувача –isUserExists(), для перевірки того чи користувач повнолітній isUserAdult() і т.д.

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

function calculateSum(param1, param2, param3){}

Тіло функції: Це набір інструкцій, які визначають, що робить функція. Тіло функції виконується, коли функція викликається.

function printParams(param1, param2, param3){
		console.log(param1, param2, param3)  // тіло функції
}

Значення, що повертається: Функція може повертати результат обчислень або значення після виконання своєї роботи. Це значення може бути використане в іншій частині програми.

function getSum(param1, param2, param3){
		const sum = param1 + param2 + param3
		return sum
}

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

function getSum(param1, param2, param3){
		const sum = param1 + param2 + param3
		return sum
}

															// Тут 1,3,4 є аргументами що передаються при виклику функції. 
															// Порядковий номер аргумента відповідає порядковому номеру параметрів функції (param1=1, param2=3, param3=4)
const result = getSum(1,3,4)  // виклик функції виконує її тіло і повертає результат
console.log(result)           // 8

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

Function declaration and expression

function declaration

Функції типу function declaration. Оголошення функції складається з ключового слова function та наступних частин:

  • Ім’я функції
  • Необовʼязковий список параметрів укладених у круглі дужки () та розділених комами.
  • Інструкції, які будуть виконані після виклику функції, укладають фігурні дужки { }.
function pow(number) {
  return number ** 2;
}

pow(2);

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

function expression

function expression — функція створюється як частина виразу чи синтаксичної конструкції. Це дозволяє створювати функцію всередині будь-якого виразу. Створення функції відбувається в правій частині виразу присвоєння = у вигляді значення, яке можна привласнити змінній.

const pow = function (number) {
  return number ** 2;
}

pow(2);

Помилка

В даному випадку наша функця є анонімною оскільки вона не має імені. pow – це ім’я змінної яка посилається на функцію, проте це ніяк не впливає на можливість виклику цієї функції через це ім’я.

Arrow function expression (Arrow function)

Стрілочні функції (Arrow functions) – це відносно новий синтаксис для визначення функцій в JavaScript, який вперше був представлений в ES6 (ECMAScript 2015). Вони надають більш стислий спосіб визначення функцій та мають кілька важливих особливостей.

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

const pow = (number) => number ** 2

pow(2);

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

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

// параметри
function greet(name) {
	console.log(`Hello, ${name}!`);
}

// аргумент 
greet('Anna'); // виведе Hello, Anna!

Параметри за замовчуванням

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

// вказуємо параметр та його значення за замовчуванням який буде дорівнювати guest
function greet(name = 'guest') {      
	console.log(`Hello, ${name}!`);
}

greet(); // в цьому випадку буде викорастані параметри за замовчуваням, виведе Hello, guest!
greet('Max'); // у цьому випадку буде використовуватись параметр якій був переданий функції, виведе, Hello, Max!

rest параметр

rest — параметр є спеціальним синтаксисом передачі невизначеної кількості аргументів функції у вигляді масиву. Цей параметр, позначається символом трикрапки … та повертає масив.

function sum(...numbers) {
	let sum = 0;  

	for (let num of numbers){
		sum += num;
	}

	return sum;
}

console.log(sum(1, 2, 3)); // 6

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

function f(arg1, ...rest, arg2) { // arg2 після ...rest ?!
  // error
}

function f(a, b, ...theArgs) { // помилки не буде
  // …
}

function myFun(a, b, ...manyMoreArgs) {
  console.log("a - ", a);
  console.log("b - ", b);
  console.log("manyMoreArgs - ", manyMoreArgs);
}

myFun("one", "two", "three", "four", "five", "six");

// Console Output:
// a - one
// b - two
// manyMoreArgs - ["three", "four", "five", "six"]

Колекція arguments та її відмінності від rest

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

function sum() {
	let total = 0;

	for (let i = 0; i < arguments.length; i++) {
		total += arguments[i];
	}

	return total;
}

console.log(sum(2, 4, 6)); // виведе 12

Крім того, arguments завжди містить усі аргументи. Ми не можемо отримати їх частково, як це було зроблено з rest. arguments – це не масив, це колекція, яка має числові індекси для доступу до аргументів і також присутній length. На цьому схожість закінчується. Жодних особливих методів у нього немає, і методи масивів він теж не підтримує. Тому зазвичай arguments перетворюють спочатку у масив перед тим як проводити подальші дії.

Стрілочні функції не мають власних arguments. У сучасному коді рекомендується надавати перевагу rest замість arguments.

Області видимості. Патерн «Раннє повернення»

Взаємодія із зовнішніми змінними

При виконнані, функція може звертатися до зовнішних змінних.

const userName = 'Anna';

function showMessage() {
	console.log(`Hello ${userName}`); // використовується зовнішня змінна  - userName
}

showMessage(); // Hello, Anna

Функція має повний доступ до зовнішньої змінної, та може її змінювати.

let userName = 'Anna';

function showMessage() {
	userName = 'Alex';
	console.log(`Hello ${userName}`);
}

showMessage(); // Hello, Alex

console.log(userName) // Anna

Локальні змінні

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

function showMessage() {
  const message = "Hello"; // локальна змінна
  console.log( message );
}

showMessage(); // Hello

console.log( message ); // <-- Помилка! Змінна недоступна поза функцією

Якщо всередині функції є змінна з таким самим ім’ям, як і зовнішня змінна, то вона перекриває зовнішню. У цьому випадку зовнішня змінна ігнорується.

const userName = 'Alex';

function showMessage() {
 const userName = 'Anna'; // оголошення локальної змінної

 const message = 'Hello, ' + userName;
  console.log(message); // Hello Anna
}

console.log( userName ); // Alex перед викликом функції showMessage

showMessage();

console.log( userName ); // Alex функція не змінила глобальну змінну

Патерн «Раннє повернення»

Оператор if…else – це основний спосіб створення розгалужень. Проте, складні та вкладені розгалуження роблять код заплутаним для розуміння.

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

function withdraw(amount, balance) {
  if (amount === 0) {
    console.log("Для проведення операції введіть суму більшу за нуль");
  } else if (amount > balance) {
    console.log("Недостатньо коштів на рахунку");
  } else {
    console.log("Операція зняття коштів проведена успішно");
  }
}

withdraw(0, 300); // "Для проведення операції введіть суму більшу за нуль"
withdraw(500, 300); // "Недостатньо коштів на рахунку"
withdraw(100, 300); // "Операція зняття коштів проведена успішно"

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

У функції може бути більше одного оператора return. Головне правило – виконання функції переривається, коли досягається оператор return, і весь код після нього ігнорується в поточному виклику функції.

Патерн «Раннього повернення» – це спосіб використання можливості завершення функції швидше за допомогою оператора return. За допомогою цього прийому отримується більш зрозумілий та плаский код, який легше піддається рефакторингу.

Ми можемо виділити всі умовні перевірки у окремі оператори if, а потім додати код, який виконується в тілі else. Ідеально – отримати плаский список умовних операторів, що слідують один за одним, з блоком коду в кінці, який виконується, якщо жоден з операторів if не був виконаний.

function withdraw(amount, balance) {
  // Якщо умова виконується, викликається console.log
  // і вихід із функції. Код після тіла if не виконується.
  if (amount === 0) {
    console.log("Для проведення операції введіть суму більшу за нуль");
    return;
  }

  // Якщо умова першого if не виконалась, його тіло пропускається
  // та інтерпретатор доходе до другого if.
  // Якщо умова виконується, викликається console.log і вихід із функції.
  // Код, що знаходиться після тіла if, не виконується.
  if (amount > balance) {
    console.log("Недостатньо коштів на рахунку");
    return;
  }

  // Якщо жоден із попередніх if не виконався,
  // інтерпретатор доходить до цього коду і виконує його.
  console.log("Операція зняття коштів проведена");
}

withdraw(0, 300); // "Для проведення операції введіть суму більшу за нуль"
withdraw(500, 300); // "Недостатньо коштів на рахунку"
withdraw(100, 300); // "Операція зняття коштів проведена"

Call stack

Stack як структура данних

Стек – це абстрактна структура даних, що працює за принципом “Last In, First Out” (LIFO), що означає “останнім прийшов – першим вийшов”. Уявіть це як стопку книг: книга, яку ви кладете на верхню частину, буде першою, яку ви заберете.

Стек має дві основні операції:

  1. Push: Додає елемент на верхівку стеку.
  2. Pop: Видаляє та повертає елемент з верхівки стеку.

У стеку немає прямого доступу до будь-якого елементу, окрім того, який знаходиться на вершині. Це називається “оглядом на вершину” (peek).

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

Стеки використовуються в багатьох аспектах програмування, таких як управління викликами функцій, управління пам’яттю, рекурсивні виклики функцій та управління викликами подій в JavaScript.

Call stack

Call stack в JavaScript – це механізм, що імплементує структуру stack та відповідає за управління порядком виклику функцій в програмі.

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

Під час виконання коду, якщо функція викликає іншу функцію, її контекст виклику також додається на вершину стеку, утворюючи так званий “стек викликів” або “call stack”.

Наприклад, розглянемо такий код:

function foo() {
  console.log("foo");
  bar();
}

function bar() {
  console.log("bar");
}

foo();

Коли викликається foo(), її контекст виклику додається на вершину call stack.

  1. Потім, у функції foo(), викликається bar(), тому контекст виклику bar() додається на вершину стеку, поки не завершиться виконання bar().
  2. Після завершення bar(), контекст виклику bar() видаляється з вершини стеку, і управління повертається до foo().
  3. Коли foo() завершить своє виконання, його контекст також буде видалено з вершини стеку.

Call stack є важливим механізмом, оскільки він дозволяє відстежувати порядок виклику функцій та управляти роботою програми. Великі стеки викликів можуть призвести до переповнення стеку (stack overflow), що виникає, коли стек викликів стає занадто великим, і система більше не може вмістити нові контексти виклику, що призводить до помилки виконання.

Переповнення стеку (Stack Overflow)

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

function foo() {
  foo(); // Рекурсивний виклик
}

foo(); // Призведе до переповнення стеку

Коли стек викликів переповнюється, виникає помилка “Uncaught RangeError: Maximum call stack size exceeded”, і виконання програми припиняється.

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

Підсумок

Функція та їх види

Функція – це блок коду, який можна викликати для виконання певних дій. Вони бувають:

  • Function Declaration: Оголошення функції за допомогою ключового слова function.
  • Function Expression: Створення функції через присвоювання її змінній.
  • Arrow Function Expression: Скорочений синтаксис для створення функцій за допомогою =>.
  • Concise Arrow Function Expression: Ще більш скорочений варіант, коли функція складається лише з return.

Параметри та аргументи

  • Параметри та аргументи: Параметри – це змінні, що оголошуються у функції. Аргументи – це значення, передані у функцію.
  • Параметри за замовчуванням: Можливість задати значення за замовчуванням для параметрів функції.
  • Невизначена кількість параметрів: Використання оператора ... для отримання необмеженої кількості аргументів.

return statement

  • Неявне повернення: Автоматичне повернення значень без використання ключового слова return.
  • Явне повернення з return: Використання return для явного повернення значень з функції.

Області видимості при використанні функцій

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

Функція в JavaScript може отримувати доступ до зовнішніх змінних, навіть змінювати їх значення.

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

Call Stack

Call stack в JavaScript – це механізм, що імплементує структуру stack та відповідає за управління порядком виклику функцій в програмі.

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