ЧАСТИНА 2
March 22, 2024
Аліса Лісняк
Full Stack web-розробник. Ментор навчальної програми Full Stack JavaScript

Основні поняття ДжаваСкрипт

Основні поняття ДжаваСкрипт

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

Синтаксис і структура

Загалом, JavaScript дуже лаконічний в плані синтаксису та мовних конструкцій (на відміну від тої ж Java), і не вимагає постійно слідкувати за форматуванням коду (відступами та пробілами), на відміну від Python.

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

Динамічна типізація

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

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

Іноді можна зустріти іншу назву цього механізму - “качина типізація” - яка походить від жартівливого “качиного тесту”. В оригіналі він звучить так: «If it looks like a duck, swims like a duck and quacks like a duck, then it probably is a duck» (“Якщо воно виглядає, як качка, плаває, як качка і крякає, як качка, тоді імовірно це качка”).

Для чого JS качина типізація? Для спрощення роботи з об’єктами та зменшення обов’язкових зв’язків та наслідувань між ними. Справа в тому, що у джаваскрипті наслідування реалізується за рахунок прототипів, що дає змогу писати такі інтерфейси, що здатні працювати з різними об’єктами. Це називається “поліморфізм” - коли єдиний інтерфейс може використовуватись для різнотипних сутностей, якщо вони мають певні однакові властивості або методи. 

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

Операції з даними

Качина типізація спрощує написання коду, але і накладає певні обмеження. Наприклад, операції зі значеннями різних типів будуть автоматично конвертуватись (“приводитись”) до одного типу даних. Якщо автоматично привести значення не вийде, то замість помилки результатом операції стане спеціальне значення. Наприклад, при спробі перемножити число і літеру, код не зламається, а результатом стане особливе значення NaN (“not-a-number”).

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

Інші математичні оператори (*, /, -) працюють тільки з числами і за неможливості конвертувати операнди до числа результатом стане Not-a-Number (NaN)

Функції

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

Функції потрібні для перевикористання однієї і тої самої логічної роботи з різними аргументами. Наприклад, маємо задачу: скласти числа 10 і 35 і помножити на 4.

Ми виконуємо дію: (10 + 35) * 4. Результат дорівнюватиме 180.

А завтра нам треба буде скласти 23 і 46 і помножити на 3. Виконуємо дію: (23 + 46) * 3 = 207

А ще згодом треба зробити (67 + 45) * 8 = 896.

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

Приклад вище можна виразити у вигляді функції:

В JavaScript є цілих три синтаксиси для визначення функцій:

  1. Декларовані функції (function declaration)
  2. Функціональні вирази (function expression)
  3. Стрілочні функції (arrow function)

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

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

Декларація функції (Function Declaration) виглядає ось так:

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

Ключове слово “return” повертає значення в якості результату і завершує роботу функції.

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

Функціональні вирази (Function Expression) - це вирази вигляду:

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

Стрілочні функції (Arrow Function) - це різновид функціональних виразів, в якому опускається ключове слово “function”, але після списку параметрів обов’язково має стояти “стрілка” ось такого вигляду:  => 

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

Окремо можна виділити “функції негайного виклику” (Immediately Invoked Function Expression - IIFE) - вони не є окремим різновидом, це просто функція, що заключена у оператор групування (круглі дужки) і одразу викликається. Виглядає це так:

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

Об’єкти та масиви

В JavaScript є 7 примітивних типів даних (string, boolean, number, null, undefined, Symbol, BigInt) і об’єкти. По суті, все що не є примітивами - є об’єктами (навіть функції).

Об’єкт - це структура даних, яка має властивості та методи. Властивості - це пари з ключа та значення, а методи - це функції, пов’язані саме з цим об’єктом. Наприклад, якщо ми захочемо описати об’єкт машини, яка має червоний колір, об’єм бака 40 літрів та вміє робити “вжум-вжум”, то в коді це можна виразити так:

В даному прикладі color, fuelVolume будуть властивостями (або їх ще називають “полями”) об’єкта, а функція go() - методом (функцією) об’єкта. Ця функція не існує у відриві від об’єкта myCar та може бути викликана тільки з його допомогою:

Особливою відмінністю джаваскрипт об’єктів від примітивних значень є те, що примітиви копіюються та порівнюються за самим значенням. Наприклад:

При порівнянні змінних з цього прикладу вони порівнюватимуться за значенням: тут 5 і тут 5, значить однакові.

З об’єктами це працюватиме інакше. Розглянемо приклад:

При створенні об'єкта у змінну cat1 поміщається тільки адреса цього об'єкту у пам'яті, відповідно при копіюванні об'єкта копіюється лише адреса, а сам об'єкт знаходиться десь у пам'яті за цією адресою.

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

При порівнянні об’єктів насправді порівнюються адреси в пам’яті

Масиви

Всі структури окрім змінних та примітивів в JavaScript представляють собою об’єкти. І масиви - не виключення. Масив - це об’єкт, в якого в якості ключів виступають індекси елементів - порядковий номер (а нумерація в програмуванні завжди йде з нуля), і значення можуть бути будь-якого типу. Наприклад, масив [12, 5, ‘привіт’, true]. У елемента під індексом 0 буде число 12, з індексом 1 - число 5, з індексом 2 - ‘привіт’ і з індексом 3 - значення true. 

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

Як і будь-які об’єкти в js, масиви мають властивості та методи для роботи з ним. Наприклад, у властивості length зберігається поточний розмір масиву (кількість елементів), а за допомогою методів можна проводити різну роботу над елементами масиву - join здатний склеїти всі елементи масива в рядок, push - додати новий елемент у кінець масиву, а reverse розгортає масив задом наперед. 

Методи масиву можна поділити на дві групи - ті, які в процесі виконання створюють новий масив і повертають посилання на нього (а старий у своїй ячейці пам’яті залишається незмінним) (до таких належать concat, filter, map) та такі, які працюють з початковим масивом та мутують його, тобто змінюють сам масив у пам’яті (серед таких методів є reverse, push, pop, shift, unshift та інші).