JavaScript
100 питань
[ITEM 1] QUESTION: Чим відрізняються var, let і const?
У сучасному мові програмування JavaScript існує три ключових способи оголошення змінних: var, let і const. Розуміння відмінностей між ними критично важливо для написання передбачуваного та якісного коду. Історично першими з'явилися змінні через var. Їх головною особливістю є функціональна область видимості, що означає доступність змінної всередині всієї функції, де вона була оголошена, незалежно від блоків коду. Крім того, var схильний до механізму спливання, або hoisting, і дозволяє повторно оголошувати ту саму змінну в межах однієї області видимості без виклику помилок, що часто призводить до важковловимих багів.
З появою стандарту ES6 арсенал розробників поповнився ключовими словами let і const, які вводять блочну область видимості. Змінні, оголошені через let, обмежені поточним блоком коду, узятим у фігурні дужки, і не можуть бути переоголошені в тій самій області видимості, хоча їхнє значення можна змінювати в процесі виконання програми. Ключове слово const працює аналогічно let, встановлюючи блочну область видимості, але накладає суворе обмеження на переприсвоєння змінної. Важливо зазначити, що якщо const використовується з об'єктами або масивами, саму вмістимість структури можна мутувати, змінюючи властивості або елементи, проте посилання на новий об'єкт присвоїти вже не вийде. У сучасній практиці розробки рекомендується використовувати const за замовчуванням для всіх незмінних посилань, а let застосовувати виключно тоді, коли значення змінної дійсно планується перезаписувати.
[ITEM 2] QUESTION: Які типи даних у JavaScript?
Система типів у JavaScript ділиться на дві основні категорії: примитивні типи даних і посилальні типи, або об'єкти. До примітивів належать сім типів: string для роботи з текстовими рядками, number для цілих чисел і чисел із плаваючою точкою, boolean для логічних значень істина чи брехня, null для позначення навмисної відсутності значення, undefined для неоголошених або неініціалізованих змінних, symbol для створення унікальних ідентифікаторів і bigint для роботи з цілими числами довільної точності. Усі примітиви незмінні й передаються за значенням. Друга категорія представлена складними структурами даних, куди входять звичайні об'єкти object, масиви array і функції function, які передаються за посиланням і можуть змінюватися в ході виконання програми.
Для визначення типу даних у коді використовується спеціальний оператор typeof, який повертає рядок із назвою типу. Однак із цим оператором пов'язані відомі нюанси та історичні особливості мови. Наприклад, перевірка типу null за допомогою typeof повертає рядок object, що є відомою історичною помилкою першої реалізації JavaScript, яку неможливо виправити з міркувань зворотній сумісності. Ще одним цікавим прикладом виступає спеціальне числове значення NaN, яке розшифровується як Not-a-Number, але фактично належить до типу number і виникає в результаті некоректних математичних операцій, таких як ділення нуля на нуль. Знання цих особливостей допомагає уникнути поширених логічних помилок під час розробки додатків.
[ITEM 3] QUESTION: Що таке hoisting?
Механізм hoisting, або підняття, є поведінкою інтерпретатора JavaScript, за якої оголошення змінних і функцій переміщуються у верхню частину їхньої області видимості перед початком виконання коду. Важливо розуміти, що фізично рядки коду не переміщуються у файлі, але на етапі компіляції пам'ять під них виділяється заздалегідь. При цьому спосіб підняття безпосередньо залежить від ключового слова, використаного під час оголошення. Змінні, створені через var, піднімаються на початок функціональної області видимості й автоматично ініціалізуються значенням undefined, через що до них можна звернутися до рядка оголошення, отримавши невизначений результат, але не помилку.
З появою let і const поведінка змінилася задля підвищення надійності коду. Оголошення через let і const також піднімаються, але вони не отримують початкового значення і поміщаються в так звану тимчасову мертву зону, або temporal dead zone. Спроба звернутися до такої змінної до рядка її фізичного оголошення в коді призведе до викиду помилки ReferenceError. Що стосується функцій, то оголошення класичних функцій через function declaration піднімаються повністю, включаючи їхнє тіло, що дозволяє викликати функцію до того, як вона була написана в тексті скрипта. При цьому важливо пам'ятати, що піднімаються виключно самі оголошення, а не операції присвоєння значень змінним.
[ITEM 4] QUESTION: Як працює == vs ===?
Оператори порівняння в JavaScript відіграють ключову роль у логіці розгалуження та прийняття рішень, причому вибір між оператором нестрогої рівності та оператором строгої рівності суттєво впливає на результат роботи програми. Оператор подвійного рівності виконує порівняння з попереднім приведенням типів, якщо операнди належать до різних категорій. Це означає, що інтерпретатор намагається привести обидва значення до загального типу даних перед зіставленням. Наприклад, вираз '5' == 5 поверне логічне значення true, оскільки рядкове значення перетворюється на числове. Така поведінка часто призводить до несподіваних результатів і важко виявлюваних помилок у коді через неявні перетворення.
Навпаки, оператор потрійного рівності виконує суворе порівняння без будь-якого приведення типів. Якщо порівнювані значення належать до різних типів даних, оператор одразу повертає false, навіть якщо їхні рядкові представлення збігаються. Наприклад, вираз '5' === 5 поверне false, оскільки лівий операнд є рядком, а правий — числом. У професійному середовищі розробників устоялося золоте правило: завжди використовувати сувору рівність задля уникнення логічних помилок і неоднозначностей. Винятком може слугувати лише перевірка на null і undefined одночасно через порівняння з null з використанням оператора подвійного рівності, хоча в сучасних проєктах частіше застосовують явні перевірки або оператор нульового злиття.
[ITEM 5] QUESTION: Що таке truthy і falsy значення?
У мові JavaScript усі значення в контексті логічних операцій діляться на дві категорії: truthy, які приводяться до істини, і falsy, які приводяться до брехні. Розуміння цього поділу критично важливо для коректної роботи умовних конструкцій на зразок оператора if, тернарного оператора або логічних операторів І та АБО. Існує суворо обмежений список falsy значень, до якого входять булеве false, число нуль 0, порожній рядок, спеціальні значення null і undefined, а також числове значення NaN. Будь-яке інше значення, що існує в мові, автоматично вважається truthy і поводиться як істина в логічних умовах.
Особливу увагу варто приділяти складним типам даних, таким як масиви та об'єкти. Розробники-початківці часто помилково вважають, що порожній масив або порожній об'єкт мають бути еквівалентні falsy, проте в JavaScript будь-які масиви та об'єкти, навіть абсолютно порожні, є truthy значеннями. Це означає, що перевірка if ([]) завжди виконається успішно, що часто стає джерелом несподіваної поведінки коду. Для того щоб явно перетворити будь-яке значення до логічного типу в коді, розробники використовують конструктор Boolean(value) або подвійне заперечення, що дозволяє точно контролювати логіку роботи додатків і уникати прихованих помилок під час оцінки умов.
QUESTION: Як працюють логічні оператори?
Логічні оператори в мові програмування JavaScript є фундаментальними інструментами для керування потоком виконання коду, перевірки умов та роботи з різними типами даних. Розуміння їхньої поведінки є критично важливим для написання чистого та ефективного коду в будь-яких веб-додатках.
На практиці розробники часто комбінують ці оператори для створення елегантних перевірок у реальних інтерфейсах. Варто дотримуватися обережності з читаéмістю коду, уникаючи занадто складних логічних ланцюжків в одному рядку, щоб інші програмісти могли легко підтримати написаний скрипт.
[ITEM 2] QUESTION: Що таке template literals?
Шаблонні рядки або template literals є потужним синтаксичним інструментом у JavaScript, який значно спрощує роботу зі строковими даними порівняно з традиційними одинарними чи подвійними лапками. Цей механізм вбудований у сучасний стандарт мови та активно застосовується у всіх фронтенд- і бекенд-фреймворках.
Перша ключова перевага полягає у використанні зворотних лапок замість звичайних, усередині яких можна вільно розміщувати текст будь-якої довжини та складності.
Друга важлива перевага — це вбудована інтерполяція, що дозволяє вставляти змінні та вирази прямо всередину рядка за допомогою спеціального синтаксису знака долара та фігурних дужок. Це повністю позбавляє розробників від громіздкої конкатенації рядків через оператор плюс, роблячи код більш читабельним.
Третьою особливістю є підтримка багаторядкових рядків «з коробки». Завдяки шаблонним рядкам більше не потрібно використовувати спеціальні символи перенесення рядка або вручну склеювати кілька фрагментів тексту для виведення великих блоків інформації.
Четверта властивість дає можливість обчислювати будь-які складні вирази прямо всередині фігурних дужок. Це можуть бути математичні операції, виклики інших функцій або тернарні оператори для відображення різного тексту залежно від поточних умов виконання програми.
П'ята особливість, орієнтована на просунутих розробників, — це теговані шаблони. Вони дозволяють передати шаблонний рядок спеціальній функції-тегу для попередньої обробки тексту, що активно використовується в бібліотеках локалізації або для безпечного екранування HTML-даних з метою запобігання вразливостям.
На практиці застосування шаблонних рядків робить вихідний код лаконічним і менш схильним до синтаксичних помилок. Вони незамінні при створенні динамічних HTML-шаблонів усередині клієнтських додатків або при формуванні складних рядків логування для налагодження програмного забезпечення.
[ITEM 3] QUESTION: Як оголосити функцію?
Створення функцій у JavaScript є базовою навичкою, оскільки функції виступають основними будівельними блоками логіки будь-якого додатка. Існує кілька основних способів оголошення функцій, кожен з яких має свої унікальні особливості застосування та поведінки в оперативній пам'яті.
На практиці вибір конкретного методу оголошення функції залежить від архітектурних вимог проєкту, необхідності використання спливання та прийнятого в команді розробників стилю написання коду. Розуміння відмінностей між цими підходами допомагає уникати важколовимих помилок під час проєктування великих додатків.
[ITEM 4] QUESTION: Як працюють стрілочні функції?
Стрілочні функції в JavaScript є компактною альтернативою традиційним функціям, запровадженою в стандарті ES6, яка змінила підхід до написання лаконічного та сучасного коду. Вони мають низку важливих синтаксичних і концептуальних особливостей, які необхідно враховувати під час розробки програм.
Перший аспект — це короткий синтаксис, що дозволяє суттєво скоротити обсяг написаного тексту. Наприклад, можна опускати круглі дужки навколо єдиного аргументу функції та не писати ключове слово return, якщо тіло функції складається всього з одного виразу.
Другий і найголовніший концептуальний момент полягає в тому, що стрілочні функції не мають власного контексту виклику. Вони завжди успадковують його з навколишнього лексичного середовища, що повністю вирішує історичну проблему втрати контексту всередині методів масивів або асинхронних таймерів.
Третя особливість полягає у відсутності в таких функцій вбудованого псевдомасиву arguments. Замість нього сучасний JavaScript рекомендує використовувати синтаксис залишкових параметрів, який працює більш передбачувано.
Четверте обмеження накладається на використання стрілочних функцій як конструкторів об'єктів. Їх неможливо викликати з оператором new, оскільки в них повністю відсутній внутрішній метод створення екземпляра та спеціальна властивість prototype.
На практиці стрілочні функції незамінні при передачі невеликих колбеків у методи на кшталт map, filter і reduce. Проте їх не варто застосовувати як методи об'єктів, якщо потрібна динамічна прив'язка контексту до об'єкта, що викликає, через ключове слово this.
[ITEM 5] QUESTION: Що таке scope (область видимості)?
Область видимості або scope у JavaScript визначає поточну доступність змінних, констант і функцій у різних частинах виконуваного програмного коду. Цей механізм є ключовим інструментом керування пам'яттю та ізоляції даних усередині великих додатків.
Перший рівень — це глобальна область видимості, змінні в якій оголошені поза будь-якими функціями та блоками. Завдяки цьому вони доступні для читання та зміни з абсолютно будь-якої точки скрипта, хоча надмірне їх використання вважається поганою практикою через ризик конфліктів імен.
Другий рівень представлений функціональною областю видимості. Змінні, створені всередині функції за допомогою ключових слів var, let або const, залишаються повністю ізольованими від зовнішнього світу і недоступними за межами цієї конкретної функції.
Третій рівень — це блочна область видимості, що з'явилася в сучасному стандарті мови. Вона обмежує видимість змінних, оголошених через let або const, рамками найближчих фігурних дужок, наприклад, усередині циклів або умовних конструкцій if.
Четвертий принцип називається лексичною областю видимості. Він означає, що доступність змінних визначається виключно фізичним розташуванням коду в текстовому редакторі в момент написання програми, а не динамічним місцем її виклику в рантаймі.
П'ятим фундаментальним поняттям є ланцюжок областей видимості. Це механізм, за допомогою якого інтерпретатор здійснює пошук потрібної змінної, починаючи зсередини поточного контексту і поступово піднімаючись рівнями вище аж до глобального простору.
На практиці глибоке розуміння цих правил дозволяє розробникам уникати випадкового перезапису глобальних даних, ефективно керувати замиканнями та писати безпечний, масштабований код, який легко тестувати й підтримувати впродовж усього життєвого циклу проєкту.
QUESTION: Що таке замикання (closure)?
Замикання в JavaScript являє собою фундаментальну концепцію, яка тісно пов'язана з тим, як мова обробляє область видимості змінних. Головна суть замикання полягає в тому, що внутрішня функція завжди пам'ятає своє лексичне середовище. Це означає, що їй доступні ті змінні, які знаходилися в місці її створення, навіть після того, як зовнішня функція повністю завершила своє виконання.
На практиці замикання активно застосовуються для створення приватних змінних, які неможливо змінити безпосередньо з глобальної області видимості. такий підхід значно підвищує безпеку і надійність коду, що розробляється. Наприклад, ви можете написати функцію-лічильник, яка інкапсулює внутрішній стан і надає спеціальні методи для його читання та зміни, повністю приховуючи саму змінну від зовнішнього втручання.
Ще одним популярним сценарієм використання є фабрики функцій. Це функції, які генерують інші функції із заздалегідь налаштованою поведінкою, наприклад, функції для логування повідомлень із певним префіксом. Також замикання лежать в основі каррування — процесу перетворення функції з кількома аргументами на набір вкладених функцій, що приймають аргументи по черзі.
Розуміння замикань критично важливе для написання ефективного коду на JavaScript. Однак необережне їх використання може призвести до небажаних наслідків. Зокрема, якщо посилання на великі зовнішні структури даних зберігаються довше, ніж це необхідно, можуть виникати витоки пам'яті.
QUESTION: Як працює this?
Ключове слово this у JavaScript визначає контекст, у якому виконується поточний код. Його значення динамічно обчислюється в момент виклику самої функції, а не в момент її оголошення. Поведінка this залежить від кількох чітких правил, що визначають спосіб виклику функції.
Якщо функція викликається як метод об'єкта, то this вказує на сам цей об'єкт. Це дозволяє методам звертатися до властивостей свого власника. У звичайній функції, викликаній без будь-якого контексту, this посилається на глобальний об'єкт window у браузері або на undefined у строгому режимі, що часто стає джерелом помилок у розробників-початківців.
Стрілочні функції поводяться зовсім інакше. Вони не мають власного this, і вони успадковують його значення із зовнішнього лексичного контексту на момент свого створення. Це робить їх ідеальними для використання всередині методів об'єктів або колбэков, де потрібно надійно зберегти контекст зовнішньої функції.
При створенні об'єкта за допомогою оператора new через функцію-конструктор або клас, this всередині цієї функції посилається на щойно створений об'єкт. Нарешті, спеціальні методи дозволяють керувати контекстом вручну. Аналізуючи місце виклику функції, можна легко визначити, на що саме вказує this у кожному конкретному випадку.
QUESTION: Що таке call, apply, bind?
Методи call, apply і bind у JavaScript призначені для ясного керування контекстом виконання функції, тобто для прив'язки певного значення this. Це потужні інструменти, які дозволяють перевикористовувати код і точно контролювати середовище виконання.
Ось основні способи їх застосування:
Метод bind особливо корисний при передачі методів як колбэков в асинхронний код або обробники подій, де контекст часто втрачається. Ще одним важливим застосуванням цих методів є запозичення методів в інших об'єктів, наприклад, використання методів масивів для роботи з псевдомасивами на кшталт arguments або NodeList. Розуміння відмінностей між call, apply і bind дозволяє писати більш гнучкий, модульний і передбачуваний код.
QUESTION: Як працювати з масивами?
Робота з масивами в JavaScript є повсякденним завданням будь-якого розробника, оскільки масиви служать основним інструментом для зберігання впорядкованих колекцій даних. Існує безліч способів створення масивів та керування їх вмістом залежно від поточних завдань проєкту.
Створити масив можна за допомогою літерала квадратних дужок [], конструктора new Array() або стативного методу Array.from(), який перетворює ітеровані об'єкти або псевдомасиви на повноцінний масив. Для зміни структури та вмісту масивів використовуються різні вбудовані методи.
Для зміни елементів застосовуються такі підходи:
Для об'єднання кількох масивів можна використовувати метод concat або сучасний оператор spread, що позначається трьома крапками, який елегантно розпаковує елементи. Щоб знайти елементи в масиві, застосовуються методи includes, що повертає логічне значення залежно від наявності елемента, та indexOf, що повертає індекс першого входження шуканого значення.
QUESTION: Які є методи масивів для ітерації?
Ітерація масивами в JavaScript виконується за допомогою вбудованих методів вищого порядку. Вони роблять код більш декларативним, читабельним і компактним порівняно з традиційними циклами на кшталт for, позбавляючи необхідності вручну керувати індексами та лічильниками.
Метод forEach просто виконує передану функцію для кожного елемента масиву, не повертаючи жодного значення. Це зручно для побічних ефектів, таких як логування в консоль або надсилання даних на сервер. Метод map перетворює кожен елемент масиву згідно з переданою функцією і повертає новий масив тієї ж довжини зі зміненими даними.
Метод filter відбирає елементи за заданою умовою, повертаючи новий масив, що складається тільки з тих елементів, для яких функція-колбэк повернула true. Метод reduce виконує згортку масиву до одного значення, послідовно застосовуючи функцію до акумулятора та поточного елемента, що робить його незамінним для підрахунку сум, групування даних або побудови складних структур.
Для пошуку конкретних елементів використовуються спеціалізовані методи. Метод find повертає перший знайдений елемент, що задовольняє умову, а findIndex повертає його індекс. Нарешті, методи перевірки some і every дозволяють оцінити вміст масиву: some перевіряє, чи відповідає умові хоча б один елемент, а every перевіряє, чи всі елементи задовольняють задану умову.
Як працює reduce?
Метод reduce в JavaScript є потужним інструментом для роботи з масивами, який дозволяє перетворити весь масив на єдине підсумкове значення. Це може бути число, рядок, масив або складний об'єкт, отриманий у результаті поетапної обробки елементів.
Базовий синтаксис цього методу виглядає як arr.reduce((acc, curr) => acc + curr, 0). Перша частина приймає callback-функцію з аргументами акумулятора та поточного елемента, а другий аргумент задає початкове значення акумулятора.
Найважливішим аспектом роботи reduce є те, що другим аргументом обов'язково або настійно рекомендується передавати початкове значення акумулятора. Це може бути нуль для математичної суми, порожній рядок для конкатенації або порожній об'єкт для групування даних.
Якщо початкове значення не вказати явно, як акумулятор автоматично візьметься перший елемент масиву. На практиці це часто призводить до несподіваних помилок на порожніх масивах або некоректних результатів під час першої ітерації.
Метод завжди повертає одне й те саме підсумкове значення, яке накопичується покроково. На кожній ітерації callback-функція повертає нове значення акумулятора, яке передається на наступний крок виконання.
Завдяки своїй винятковій гнучкості, метод дозволяє не лише обчислювати примітивні типи даних, а й формувати нові складні структури. З його допомогою можна легко згрупувати масив користувачів за віком або порахувати частоту зустрічаемости слів у тексті.
Попри універсальність, метод іноді вважається складним для читання початківцями-розробниками. У простих сценаріях його варто замінювати на більш прозорі й зрозумілі методи на кшталт map або filter, щоб не ускладнювати підтримку коду.
Як працювати з об'єктами?
Робота з об'єктами в JavaScript є фундаментальною навичкою для кожного розробника, оскільки об'єкти використовуються повсюдно для зберігання структурованих даних. Вони дозволяють об'єднувати розрізнені змінні та функції в єдині логічні сутності для зручного керування станом додатку.
Створення об'єктів найчастіше виконується за допомогою літерала об'єкта через фігурні дужки. Це є сучасним стандартом індустрії, хоча технічно для тих самих цілей можна використовувати й універсальний конструктор new Object().
Для отримання або зміни значень властивостей доступно два основні способи. Точкова нотація застосовується, коли ім'я ключа заздалегідь відоме і є валідним ідентифікатором змінної без пробілів та спецсимволів.
Квадратні дужки незамінні при роботі з динамічними ключами, значення яких обчислюються під час виконання програми, або коли ключі містять пробіли, дефіси та інші спецсимволи.
Для аналізу вмісту об'єкта стандартна бібліотека надає зручні статичні методи. Object.keys повертає масив ключів, Object.values — масив значень, а Object.entries — масив пар ключ-значення, що є критично важливим при ітерації.
Сучасний синтаксис дозволяє легко об'єднувати об'єкти за допомогою оператора spread. Ви можете записати новий об'єкт із комбінації двох старих, де властивості послідовно перезаписуватимуться зліва направо.
Для копіювання властивостей історично також використовується метод Object.assign, який дозволяє об'єднати кілька джерел у цільовий об'єкт. Проте варто пам'ятати, що обидва ці підходи виконують лише поверхневе копіювання, залишаючи вкладені об'єкти посилальними.
Що таке деструктуризація?
Деструктуризація в JavaScript є зручним синтаксичним цукром, який дозволяє витягувати дані з масивів та об'єктів і записувати їх у змінні коротшим і наочнішим способом. Цей підхід значно скорочує обсяг шаблонного коду.
При роботі з масивами деструктуризація дозволяє присвоювати елементам нові змінні за їхнім суворим порядком слідування. Наприклад, конструкт виду const [a, b] витягує перші два елементи масиву у відповідні константи.
Для об'єктів зіставлення відбувається не за порядком, а за точними іменами ключів. Запис виду const {name, age} автоматично витягує властивості name та age з переданого об'єкта user незалежно від їхнього розташування у вихідній структурі.
Якщо ім'я існуючого ключа не підходить для поточного контексту або конфліктує з уже оголошеними змінними, мова дозволяє перейменувати змінну прямо під час деструктуризації. Для цього використовується двокрапка, наприклад, при витяганні ключа name у нову змінну n.
Додаткову гнучкість надають значення за замовчуванням, які можна задати на випадок відсутності властивості. Їх можна вказати, записавши знак рівності та потрібне значення прямо всередині шаблону деструктуризації.
Крім того, підтримується глибока або вкладена деструктуризація. Вона дозволяє дістатися до властивостей внутрішніх об'єктів одним рядком коду, витягуючи властивість із вкладеного об'єкта за допомогою комбінації двокрапки та фігурних дужок.
Що таке spread і rest?
Оператори spread і rest в JavaScript записуються однаково у вигляді трьох крапок, але виконують протилежні за змістом завдання залежно від контексту їх застосування. Вони значно спрощують маніпуляції з наборами даних та колекціями.
Оператор spread виконує роль розпакувача, розгортаючи ітеровані об'єкти, такі як масиви чи рядки, в окремі елементи. З його допомогою можна легко об'єднувати масиви, створюючи нові колекції з елементів старих.
Також spread активно застосовується для клонування та доповнення об'єктів новими ключами та значеннями в літеральній формі, замінюючи застарілі методи об'єднання даних.
Оператор rest, навпаки, виконує роль збиральника, який об'єднує безліч розрізнених аргументів у єдиний масив. Це особливо корисно при створенні функцій зі змінною кількістю параметрів.
Коли точна кількість аргументів заздалегідь невідома, їх зручно перехопити через rest-параметр у сигнатурі функції, щоб обробити у вигляді стандартного масиву.
Той самий механізм успішного збору даних застосовується і всередині деструктуризації. Там можна забрати перший елемент масиву в окрему змінну, а решту запакувати в новий масив за допомогою rest-синтаксису.
Як глибоко скопіювати об'єкт?
Створення глибокої копії об'єкта в JavaScript необхідне тоді, коли потрібно повністю дублювати складну структуру даних з усіма вкладеними об'єктами та масивами. Це гарантує, що будь-які зміни в копії не торкнуться оригінального об'єкта.
Найсучаснішим і найпереповітнішим способом на сьогодні є використання вбудованої функції structuredClone. Вона коректно обробляє більшість вбудованих типів даних, зберігає циклічні посилання і є частиною стандарту браузерів та Node.js.
Історично популярний підхід із серіалізацією через JSON.parse(JSON.stringify(obj)) досі зустрічається в чужому коді, але має критичні обмеження. Він повністю видаляє методи функцій, не вміє працювати зі спеціальними типами на кшталт Map, Set, Date і падає з помилкою при циклічних посиланнях.
У великих проєктах зі старою кодовою базою часто застосовують перевірені сторонні бібліотеки. Наприклад, утилітний метод cloneDeep з популярної екосистеми Lodash надійно вирішує всі проблеми глибокого клонування протягом багатьох років.
У крайніх випадках, коли потрібна специфічна бізнес-логіка або суворий контроль над типами, що копіюються, розробники пишуть власні рекурсивні функції обходу властивостей об'єкта.
Важливо завжди пам'ятати, що звичайний оператор spread або метод Object.assign для глибокого копіювання категорично не підходять. Вони виконують виключно поверхневе копіювання і дублюють посилання на всі внутрішні вкладені об'єкти.
QUESTION: Що таке Map і Set?
У сучасному мові JavaScript структури даних Map і Set є потужними вбудованими колекціями, які суттєво розширюють можливості розробника порівняно з традиційними об'єктами та масивами. Ці інструменти забезпечують високу продуктивність і зручність під час вирішення повсякденних завдань програмування.
Колекція Map є класичним словником пар ключ-значення. Головною перевагою Map є те, що в ролі ключа може виступати абсолютно будь-який тип даних, включаючи об'єкти, функції та примітивні типи, тоді як у звичайних об'єктах ключами можуть бути лише рядки або символи.
Для створення нової колекції використовується простий синтаксис конструктора new Map(). Після цього для наповнення та керування даними застосовуються стандартні методи: set() для додавання пари, get() для отримання значення за ключем, а також метод has(), що дозволяє швидко перевірити наявність ключа в колекції.
Колекція Set, у свою чергу, є набором унікальних значень будь-якого типу, де кожен елемент може зустрічатися лише один раз. Це робить Set ідеальним інструментом для фільтрації дублікатів з масивів або перевірки наявності елементів у великих наборах даних.
Основні методи для роботи з набором включають add() для додавання нового елемента, has() для перевірки його наявності та delete() для видалення. Для перетворення набору назад у масив можна використовувати оператор поширення або метод Array.from().
Важливим доповненням стандартних колекцій є їхні «слабкі» аналоги — WeakMap і WeakSet. Головна особливість WeakMap і WeakSet полягає в тому, що вони використовують слабкі посилання на об'єкти-ключі або елементи, що дозволяє збирачу сміття автоматично видаляти їх з пам'яті.
Використання WeakMap і WeakSet запобігає витокам пам'яті у складних додатках, оскільки елементи автоматично видаляються, щойно на них зникають інші посилання. Це робить їх незамінними для кешування даних та зберігання приватних властивостей об'єктів.
[ITEM 2] QUESTION: Що таке Symbol?
Тип даних Symbol у JavaScript є унікальним і незмінним примітивним типом даних, який був запроваджений у стандарт ES6 для вирішення завдання створення прихованих і унікальних ідентифікаторів властивостей об'єктів.
Головна особливість символів полягає в тому, що навіть якщо два символи створені з абсолютно однаковим описом або взагалі без нього, вони все одно суворо не рівні між собою. Наприклад, виклик Symbol('desc') завжди повертатиме унікальне значення, і умова Symbol('desc') !== Symbol('desc') завжди поверне істину.
На практиці символи найчастіше використовуються для створення унікальних ключів властивостей об'єктів, які не конфліктуватимуть з іншими властивостями. Це особливо корисно під час розробки бібліотек або додавання метаданих, прихованих від стандартних циклів перебору на зразок for...in.
Для ситуацій, коли необхідно використовувати той самий символ у різних частинах додатка, існує глобальний реєстр символів. Доступ до нього здійснюється через спеціальні методи роботи з глобальним простором імен:
Крім того, у JavaScript існує набір так званих добре відомих символів (well-known symbols), які вбудовані в мову і дозволяють розробникам змінювати поведінку вбудованих механізмів об'єктів.
Яскравим прикладом такого символу є Symbol.iterator, який визначає ітератор за замовчуванням для об'єкта. Реалізувавши цей метод, розробник отримує можливість використовувати свій власний об'єкт у циклі for...of.
[ITEM 3] QUESTION: Як працювати з рядками?
Робота з рядками в JavaScript є одним із найчастіших завдань під час розробки веб-додатків. Для ефективного вирішення таких завдань мова надає багатий арсенал вбудованих методів і властивостей.
Базовою інформацією про рядок керує властивість length, що повертає його довжину в символах. Для отримання конкретного символу за індексом використовується метод charAt() або стандартне звернення за індексом через квадратні дужки.
Коли потрібно витягти певний фрагмент тексту, на допомогу приходять методи slice() і substring(), що дозволяють вирізати підрядок за заданими індексами початку і кінця. Вони зручні під час обробки введення користувача та форматування виведення.
Для трансформації тексту активно застосовуються методи toLowerCase() і toUpperCase(), які приводять усі символи рядка до нижнього чи верхнього регістру. Метод trim() ефективно видаляє пробіли та символи перенесення рядків з обох кінців текстової змінної.
Якщо перед розробником стоїть завдання об'єднати масив рядків у єдиний текст або, навпаки, розбити рядок на масив за певним розділювачем, використовуються методи join() і split(). Це незамінно під час парсингу URL-адрес або CSV-файлів.
Пошук підрядків у тексті виконується за допомогою зручних булевих методів includes(), startsWith() і endsWith(), які перевіряють наявність тексту, а також початок і кінець рядка відповідно. Для зміни вмісту рядків застосовуються методи replace() для заміни першого знайденого збігу та replaceAll() для глобальної заміни.
У випадках, коли потрібне форматування виведення, наприклад, для додавання провідних нулів до чисел, незамінні методи padStart() і padEnd(). Вони доповнюють рядок до заданої довжини вказаними символами, забезпечуючи красиве вирівнювання даних.
[ITEM 4] QUESTION: Що таке регулярні вирази?
Регулярні вирази в JavaScript є потужним і лаконічним інструментом для пошуку, вилучення та маніпуляції текстовими даними на основі заданих шаблонів. Вони незамінні під час валідації форм, розбору логів та обробки великих обсягів тексту.
Створити регулярний вираз можна двома основними способами:
Для базової перевірки відповідності рядка шаблону застосовується метод test(), який повертає логічне значення true або false залежно від наявності збігів. Цей метод ідеально підходить для швидкої валідації email-адрес або телефонних номерів.
Коли потрібно не просто перевірити факт наявності, а й знайти конкретні збіги, використовуються рядкові методи match() і більш продвинутий matchAll(), що повертає ітератор з усіма деталями збігів, включаючи групи захоплення.
Для виконання масових замін тексту за шаблоном застосовується метод replace(), що приймає регулярний вираз як перший аргумент. Це дозволяє швидко перетворювати формати дати або видаляти небажані символи з тексту.
Важливу роль у роботі з регулярними виразами відіграють прапорці, які модифікують поведінку пошуку. Прапорець g вмикає глобальний пошук усіх збігів, а прапорець i робить пошук нечутливим до регістру символів.
Додаткові прапорці включають m для багаторядкового режиму, s для відповідності точки будь-яким символам, включаючи переведення рядка, а також прапорець u для повної підтримки стандарту Unicode, що критично під час роботи з сучасними алфавітами та емодзі.
[ITEM 5] QUESTION: Як працювати з числами?
Робота з числами в JavaScript охоплює безліч сценаріїв — від базових арифметичних операцій до складного математичного моделювання та обробки великих обсягів даних. Мова надає вбудовані інструменти для безпечної конвертації та форматування чисел.
Для безпечного перетворення рядків у числові значення використовуються глобальні функції parseInt() для цілих чисел і parseFloat() для чисел із плаваючою комою. Перевірка коректності числових даних виконується за допомогою методів Number.isNaN() і Number.isFinite().
Коли потрібно відформатувати число для виведення на екран, наприклад, обмежити кількість знаків після коми у фінансових розрахунках, незамінний метод toFixed(digits). Він повертає рядок із заданою кількістю знаків у дробовій частині, а метод toPrecision() керує загальною точністю числа.
Широкі можливості для математичних обчислень надає вбудований об'єкт Math. Він містить методи для округлення round(), округлення у менший бік floor(), округлення у більший бік ceil(), а також метод random() для генерації випадкових чисел.
Метод Math.random() генерує випадкові числа в діапазоні від нуля до одиниці, що часто використовується в ігрових алгоритмах, генераторах паролів та автоматичних тестах. Комбінуючи його з іншими методами, можна отримувати випадкові цілі числа в заданому діапазоні.
Для роботи з цілими числами довільної довжини, що виходять за межі стандартного безпечного числового діапазону (перевищують Number.MAX_SAFE_INTEGER), до сучасного JavaScript було додано примітивний тип BigInt. Він створюється шляхом додавання суфікса n до числа або виклику функції BigInt(), дозволяючи виконувати точні обчислення з величезними цілими числами без втрати точності.
QUESTION: Що таке асинхронність в JS?
Асинхронність у JavaScript є фундаментальною концепцією, яка дозволяє виконувати тривалі операції, такі як мережеві запити, читання файлів або таймери, не зупиняючи роботу всього додатка. Щоб зрозуміти, чому це необхідно, потрібно пам'ятати, що JavaScript є однопотоковою мовою програмування. Це означає, що він може виконувати лише одне завдання за раз у своєму головному потоці виконання. Якби всі операції виконувалися суворо послідовно та синхронно, будь-яке звернення до сервера призводило б до зависання інтерфейсу, роблячи сторінку абсолютно нечутливою до дій користувача.
Щоб уникнути цього блокування, JavaScript використовує механізм асинхронності. Коли код стикається з довгою операцією, вона передається на виконання у зовнішнє середовище, наприклад, у Web APIs браузера або середовище Node.js. Основний потік тим часом продовжує виконувати наступні рядки коду. Коли фонове завдання завершується, результат повертається у вигляді події або функції зворотного виклику.
За десятиліття еволюції мови підхід до написання асинхронного коду зазнавав значних змін. Історично першими інструментами були callbacks, тобто функції зворотного виклику, що передаються як аргументи. Пізніше на зміну їм прийшли Promises, які зробили код більш читабельним і дозволили уникнути глибокої вкладеності. Вершиною зручності стали сучасні синтаксичні конструкції async/await, побудовані поверх промісів і дозволяють писати асинхронний код так, ніби він є синхронним.
Координує всю цю складну систему спеціальний механізм, званий Event Loop. Він постійно стежить за станом стека викликів і черг завдань, розподіляючи готові до виконання функції. Завдяки цьому розробники можуть створювати швидкі, чуйні веб-додатки, здатні ефективно обробляти безліч фонових процесів одночасно, забезпечуючи чудовий користувацький досвід без затримок і зависань інтерфейсу.
[ITEM 2] QUESTION: Що таке Event Loop?
Event Loop або цикл подій є фундаментальним механізмом у JavaScript, який забезпечує асинхронну поведінку мови, незважаючи на її однопотокову природу. Без цього механізму було б неможливо уявити сучасну веб-розробку, оскільки він координує виконання коду, обробку подій, мережеві запити та відмальовування інтерфейсу.
В основі роботи Event Loop лежить кілька ключових компонентів. Перший із них — це Call Stack або стек викликів, де безпосередньо виконуються функції вашого синхронного коду. Коли інтерпретатор зустрічає функцію, він додає її в стек, а після завершення роботи видаляє. Якщо функція викликає іншу, та поміщається поверх першої.
Другий важливий елемент — це черги завдань. Коли асинхронна операція, така як таймер або запит до сервера, завершується, її результат відправляється в одну з черг. Тут існує поділ на Task Queue, куди потрапляють стандартні колбеки від setTimeout або подій DOM, і Microtask Queue, призначена спеціально для промісів і мутацій DOM.
Сам Event Loop є нескінченним циклом, який постійно перевіряє стан стека викликів. Якщо Call Stack порожній, цикл звертає свою увагу на черги. При цьому діє суворе правило пріоритету: Microtask Queue обробляється завжди раніше, ніж Task Queue. Event Loop переносить усі завдання з черги мірозавдань у стек викликів і виконує їх доти, доки ця черга не спорожніє. Тільки після цього береться одне завдання зі звичайної черги завдань.
Такий підхід гарантує, що ланцюжки промісів та асинхронні операції будуть виконані якнайшвидше, зберігаючи передбачуваний порядок виконання коду та запобігаючи гонкам даних в однопотоковому середовищі виконання.
[ITEM 3] QUESTION: Що таке callbacks?
Callback або функція зворотного виклику — це традиційний підхід до організації асинхронного програмування в JavaScript. Суть концепції полягає в тому, що звичайна функція передається як аргумент в іншу функцію і викликається останньою після того, як завершиться певна операція або настане задана подія.
Класичним прикладом використання колбеків є операції читання файлів, таймери або надсилання мережевих запитів. Наприклад, під час запиту даних з сервера ви передаєте функцію, яка спрацює лише тоді, коли прийде відповідь. У стандартній практиці Node.js прийнято використовувати підхід error-first callbacks. У такому разі перший аргумент, що передається у функцію зворотного виклику, зарезервований під об'єкт помилки. Якщо під час виконання операції сталася помилка, вона потрапить у цей аргумент. Якщо ж усе минуло успішно, перший аргумент дорівнюватиме null або undefined, а корисні дані передадуться у другому та наступних аргументах.
Однак використання чистих callbacks має суттєвий недолік, відомий як callback hell або ад колбеків. Коли потрібно виконати кілька асинхронних операцій послідовно, одну за одною, розробники змушені глибоко вкладати функції одна в одну. Такий код стає вкрай складним для читання, тестування та підтримки, а також сильно ускладнює обробку помилок.
Саме через ці архітектурні проблеми спільнота поступово відмовилася від повсюдного використання callbacks на користь доскональніших конструкцій, таких як Promises та async/await. Незважаючи на це, розуміння того, як працюють функції зворотного виклику, залишається критично важливим, оскільки вони досі глибоко інтегровані в багато старих бібліотек і базових методів браузерних API, таких як слухачі подій addEventListener.
[ITEM 4] QUESTION: Що таке Promise?
Promise або проміс у JavaScript — це спеціальний об'єкт, який служить для представлення успішного або невдалиго завершення асинхронної операції, а також її результуючого значення. Проміс можна уявити як обіцянку повернути якийсь результат у майбутньому: ви не отримуєте дані моментально, але знаєте, що вони з'являться пізніше, або отримаєте повідомлення про помилку.
У будь-який момент часу проміс перебуває в одному з трьох можливих станів. Початковим станом є pending або очікування, коли асинхронне завдання ще виконується, і результат поки невідомий. Якщо операція завершується успішно, проміс переходить у стан fulfilled або виконано, повертаючи підсумкове значення. У разі виникнення помилки проміс переходить у стан rejected або відхилено, повертаючи причину збою. Важливо зазначити, що після того, як проміс перейшов у стан fulfilled або rejected, його статус уже ніколи не зможе змінитися.
Для роботи з результатами промісів використовуються спеціальні методи екземпляра: then(), catch() та finally(). Метод then() приймає дві функції зворотного виклику: перша виконується при успішному виконанні промісу, а друга — при його відхиленні. Метод catch() є зручним синтаксичним цукром для обробки помилок і викликається, якщо проміс був відхилений. Метод finally() виконується в будь-якому випадку після завершення роботи промісу, незалежно від його успіху чи неуспіху, що зручно для очищення ресурсів або приховування індикаторів завантаження.
Головною перевагою промісів перед старими колбеками є їхня здатність до ланцюжків або чейнінгу. Ви можете викликати метод then() послідовно, передаючи результат однієї асинхронної операції на вхід наступній. Це дозволяє писати чистий, плаский і легко читаваний код, повністю уникаючи проблеми глибокої вкладеності функцій.
[ITEM 5] QUESTION: Як створити Promise?
Створення промісу в JavaScript здійснюється за допомогою спеціального конструктора new Promise(). Цей конструктор приймає як аргумент функцію-виконавець, яку розробники часто називають екзекутором. Ця функція автоматично запускається одразу після створення промісу і приймає два обов'язкові параметри, які самі по собі є функціями: resolve та reject.
Перша функція, resolve, використовується для успішного завершення операції. Коли асинхронне завдання в тілі екзекутора успішно виконано, ви викликаєте resolve(value), передаючи їй отриманий результат. Це переводить проміс зі стану очікування в стан виконання, роблячи значення доступним для методу then(). Друга функція, reject, викликається в тому разі, якщо в процесі виконання завдання сталася помилка або умова не була виконана. Ви викликаєте reject(error), передаючи об'єкт помилки, що переводить проміс у стан відхилення та активує обробники помилок.
Крім ручного створення промісів через конструктор за допомогою екзекутора, JavaScript надає корисні статичні методи для швидкого створення готових промісів. Наприклад, метод Promise.resolve(value) повертає проміс, який уже перебуває в стані fulfilled з переданим значенням. Це буває корисно, коли вам потрібно уніфікувати інтерфейс функції, яка в деяких випадках може повертати синхронне значення, а в інших — асинхронний проміс.
Аналогічним чином працює метод Promise.reject(error), який одразу створює і повертає відхилений проміс із вказаною причиною помилки. Використання цих статичних методів значно спрощує написання допоміжного коду та дозволяє ефективно керувати потоком асинхронних даних у сучасних додатках без зайвого бойлерплейту.
Як обробляти помилки Promise?
Обробка помилок у промісах у JavaScript є фундаментальною навичкою для створення стабільних і надійних вебдодатків. Основним інструментом для цього слугує метод .catch(error => {}), який перехоплює будь-які збої та відхилення, що виникають у попередніх кроках асинхронного ланцюжка. Альтернативним варіантом є передача другого аргументу в метод .then(null, onRejected), проте на практиці перший спосіб використовується набагато частіше завдяки своїй наочності та зручності читання коду.
Головна перевага методу .catch полягає в тому, що catch ловить усі помилки в ланцюжку, незалежно від того, на якому саме етапі асинхронного виконання стався збій. Це дозволяє централізовано обробляти винятки, а не писати кремезні обробники для кожного окремого кроку. Якщо в процесі виконання коду виникає непередбачена ситуація, виконання перенаправляється в найближчий блок catch.
Важливо пам'ятати про ситуацію, коли проміс відхиляється, але для нього не передбачено жодної обробки. У сучасних середовищах виконання це призводить до виникнення події unhandledrejection на глобальному об'єкті, яка може спричинити аварійне завершення процесу в Node.js або записати попередження в консоль браузера. Щоб уникнути подібних проблем, завжди рекомендується завершувати ланцюжки промісів викликом методу catch.
Для виконання коду, який має бути запущений у будь-якому разі — незалежно від того, чи завершився проміс успішно, чи з помилкою, — використовується метод finally(). Він приймає callback-функцію без аргументів і чудово підходить для очищення ресурсів, закриття з'єднань із базою даних або приховування індикатор завантаження на користувацькому інтерфейсі. Грамотне поєднання всіх цих інструментів дозволяє повністю контролювати життєвий цикл асинхронних операцій у ваших проєктах.
Що таке Promise.all?
Метод Promise.all є потужним інструментом для паралельного виконання кількох асинхронних операцій у JavaScript. Синтаксично він приймає ітерований об'єкт, найчастіше масив, що містить проміси, наприклад, у вигляді конструкції Promise.all([p1, p2, p3]), і запускає їх одночасно, а не послідовно. Це кардинально підвищує продуктивність додатків, коли вам потрібно запитати незалежні дані з кількох джерел.
Головна особливість цього методу полягає в тому, що він чекає всі promises перед тим, як продовжити виконання подальшого коду. Коли кожен із переданих промісів успішно завершується, метод повертає масив результатів, де кожен елемент відповідає результату виконання вихідного промісу в тому ж порядку, в якому вони були передані у вхідний масив. Це дуже зручно для деструктуризації отриманих даних, наприклад, під час одночасного завантаження профілю користувача, списку його замовлень і налаштувань інтерфейсу.
Проте даний підхід має критичну особливість, яку необхідно враховувати під час проєктування архітектури: він відхиляється за першої ж помилки. Якщо хоча б один із промісів у масиві завершиться з помилкою (перейде в стан rejected), весь Promise.all негайно припиняє очікування решти й повертає цю помилку. Інші проміси при цьому продовжують виконуватися у фоновому режимі, але їхні результати будуть проігноровані. Тому Promise.all ідеально підходить для завдань, де всі запити критично важливі для роботи екрана, і частковий успіх не має сенсу.
Що таке Promise.race, allSettled, any?
У сучасному JavaScript, крім класичного Promise.all, існує ще ціла низка допоміжних методів для роботи з колекціями асинхронних завдань, кожен із яких вирішує специфічні бізнес-завдання. Метод race призначений для ситуацій, коли вам важливий найшвидший результат, незалежно від того, чи буде він успішним, чи помилковим. Він повертає перший проміс, що завершився, серед переданих, будь то успіх чи помилка, що часто застосовується для реалізації таймаутів мережевих запитів.
Цілком інакше працює метод allSettled, який був доданий для ситуацій, коли необхідно дочекатися завершення абсолютно всіх завдань, незалежно від їхнього результату. Метод allSettled чекає всі проміси й повертає масив об'єктів, що описують статус кожного з них зі значеннями або результату, або причини помилки. Найважливішою властивістю цього методу є те, що allSettled ніколи не відхиляється загальним промісом, що робить його незамінним інструментом для аналітики або пакетної обробки даних, де часткові збої не повинні зупиняти весь процес.
Нарешті, метод any орієнтований виключно на пошук першого успішного результату. Він повертає найперший успішний проміс, ігноруючи будь-які попередні помилки. Якщо ж усі передані завдання завершилися невдало, метод any відхиляється, повертаючи спеціальний об'єкт AggregateError, що містить усі накопичені помилки. Така поведінка робить any ідеальним вибором для реалізації механізмів резервного копіювання або отримання даних із кількох дзеркал одного й того самого сервера.
Як працює async/await?
Конструкція async/await є зручним синтаксичним цукром над стандартними Promises, який дозволяє писати асинхронний код так, ніби він є синхронним, що значно спрощує його читання та підтримку. Будь-яка async function завжди автоматично повертає Promise, навіть якщо всередині неї явно не вказано оператор return. Це гарантує, що код, який викликає, завжди зможе продовжити ланцюжок через метод then або використати ключове слово await.
Ключовим елементом цього синтаксису є оператор await, який може застосовуватися тільки всередині async функцій. Коли інтерпретатор доходить до рядка з await, він призупиняє виконання поточної функції та чекає дозволу вказаного промісу, звільняючи потік для виконання інших завдань у циклі подій. Щойно проміс успішно вирішується, виконання функції відновлюється, а значення промісу записується в змінну, що позбавляє розробника необхідності писати безліч вкладених callback-функцій.
Наприклад, під час розробки кліентських додатків ви можете послідовно надсилати запити на сервер, чекаючи на відповідь від кожного з них у зрозумілій лінійній структурі. За своєю суттю async/await не привносить принципово нових можливостей у рушій JavaScript, але він кардинально змінює підхід до написання коду, зменшуючи ймовірність помилок за рахунок природної послідовності кроків і спрощуючи налагодження складних асинхронних алгоритмів.
Як обробляти помилки в async/await?
Правильна обробка помилок в async/await є запорукою створення відмовостійких додатків на JavaScript. Оскільки традиційні методи .catch тут не завжди застосовні напряму до кожного рядка, стандартним підходом є використання класичної конструкції try/catch. Ви загортаєте потенційно небезпечний асинхронний код у блок try { await ... } і в разі виникнення будь-якої проблеми перехоплюєте об'єкт помилки в блоці catch (e) { }, що дозволяє гнучко реагувати на збої, виводити повідомлення користувачеві або логувати інциденти.
Альтернативним варіантом точкової обробки є додавання стандартного методу .catch() безпосередньо на виклику асинхронної функції, що повертає проміс, якщо ви віддаєте перевагу функціональному стилю. Для реалізації обов'язкових завершальних дій, таких як звільнення пам'яті або закриття з'єднань, використовується блок finally, який виконається в самому кінці незалежно від того, чи завершився блок try успіхом, чи зловив виняток. Ви можете успішно комбінувати ці підходи для створення багаторівневого захисту критичних ділянок вашого коду.
Якщо розробник забуває обгорнути виклик асинхронної функції в блок try/catch або не обробляє проміс, що нею повертається, у системі виникають необроблені помилки, які призводять до події unhandledrejection. Щоб додаток не падав несподівано в production-середовищі, рекомендується на рівні глобального додатка налаштувати перехоплення цієї події, що дозволить централізовано збирати статистику збоїв і коректно завершувати критичні фонові процеси.
QUESTION: Як виконати async паралельно?
Для того щоб ефективно виконати асинхронні операції паралельно в сучасному JavaScript, найбільш надійним і елегантним рішенням є використання вбудованої конструкції await Promise.all([fn1(), fn2()]). Головна помилка розробників-початківців полягає в тому, що вони послідовно викликають кожну асинхронну функцію через окремі оператори await, що призводить до непотрібного блокування потоку виконання та збільшення загального часу очікування. Коли ви передаєте масив промісів у Promise.all, усі асинхронні завдання запускаються одночасно, а виконання коду призупиняється рівно до того моменту, поки не завершиться найдовший із них.
Результати виконання всіх переданих функцій повертаються у вигляді єдиного впорядкованого масиву, що дозволяє легко отримати до них доступ. Для зручності роботи часто застосовується синтаксис деструктуризації, наприклад, const [a, b] = await Promise.all([fetchDataA(), fetchDataB()]), що робить код чистим і читабельним. Проте варто пам'ятати важливу особливість: якщо хоча б один із промісів у масиві завершиться з помилкою (буде відхилений), увесь Promise.all негайно завершиться з помилкою, а решту результатів буде проігноровано.
Якщо у вашій бізнес-логіці критично важливо отримати результати абсолютно всіх запитів, незалежно від того, завершилися вони успіхом чи падінням, замість Promise.all слід використовувати метод Promise.allSettled. Він повертає масив об'єктів, що описують статус кожного промісу (fulfilled або rejected) разом із його значенням чи причиною помилки. Це дає максимальну гнучкість під час обробки пакетних мережевих запитів, читання файлів або виконання інших незалежних одна від одної асинхронних операцій у веб-додатку.
QUESTION: Що таке setTimeout і setInterval?
Функції setTimeout і setInterval є фундаментальними інструментами в JavaScript для керування часом та планування виконання коду в асинхронному режимі через механізм циклу подій. Метод setTimeout(fn, ms) використовується для того, щоб виконати передану функцію fn один раз після того, як пройде задана кількість мілісекунд ms. Це корисно для створення відкладених дій, наприклад, приховування спливаючого сповіщення через три секунди або реалізації дебаунсу під час введення тексту в поле пошуку.
У свою чергу, метод setInterval(fn, ms) призначений для циклічного повторення вказаної функції через рівні інтервали часу. Він продовжує працювати доти, доки виконання не буде зупинено примусово. Обидва методи повертають унікальний числовий ідентифікатор таймера (ID), який використовується для їх скасування. Для скасування запланованого одиничного виклику застосовується функція clearTimeout(timerId), а для зупинки повторюваного інтервалу — clearInterval(timerId), що є обов'язковою вимогою для запобігання витокам пам'яті, наприклад, під час розмонтування компонентів у фреймворках.
Важливо розуміти особливості роботи цих таймерів у браузері. Вказана затримка в мілісекундах не є гарантованим часом точного спрацьовування, а є лише мінімально допустимою паузою. Через однопотокову природу JavaScript та особливості роботи циклу подій (Event Loop), якщо основний потік зайнятий важкими обчисленнями, таймер чекатиме своєї черги. Крім того, у сучасних браузерах для вкладених таймерів або неактивних вкладок існує апаратне обмеження мінімальної затримки, яка зазвичай становить близько 4 мілісекунд.
QUESTION: Що таке requestAnimationFrame?
Метод requestAnimationFrame є спеціалізованим програмним інтерфейсом у JavaScript, створеним спеціально для плавної реалізації візуальних анімацій у веб-браузері. Головна його відмінність від класичного setInterval полягає в тому, що він жорстко синхронізований із частотою оновлення екрана монітора, яка у більшості сучасних дисплеїв становить 60 кадрів на секунду (fps), а на просунутих пристроях може досягати 120 Гц і вище.
Коли ви передаєте функцію зворотного виклику в requestAnimationFrame, браузер гарантує, що код анімації виконається рівно перед черговим перемальовуванням сторінки (repaint). Це дозволяє уникнути таких неприємних ефектів, як пригальмовування, ривки та розриви кадрів (tearing), які часто виникають під час спроби анімувати елементи через фіксовані інтервали часу. Метод повертає числовий ідентифікатор запиту, який можна передати у функцію cancelAnimationFrame, якщо анімацію потрібно перервати до її завершення, наприклад, під час закриття модального вікна.
Ще однією найважливішою перевагою requestAnimationFrame є його енергоефективність та турбота про продуктивність системи. Якщо користувач переключається на іншу вкладку браузера або згортає вікно, браузер автоматично призупиняє виконання всіх анімацій, зав'язаних на цей метод. Це дозволяє радикально знизити навантаження на центральний процесор, заощадити заряд батареї на мобільних пристроях та запобігти перегріву заліза, в той час як setInterval продовжував би марно навантажувати систему у фоновому режимі.
QUESTION: Що таке fetch API?
Інтерфейс Fetch API є сучасним, потужним і стандартним способом виконання мережевих HTTP-запитів у JavaScript, що прийшов на заміну застарілому та громіздкому XMLHttpRequest. Базове використання зводиться до виклику функції fetch(url, options), куди передається цільова адреса та необов'язковий об'єкт із налаштуваннями, такими як метод, заголовки та тіло запиту. У відповідь метод повертає Promise, який вирішується об'єктом відповіді (Response) одразу ж, як тільки сервер надсилає заголовки, не дожидаючись завантаження всього тіла відповіді.
Для вилучення корисних даних із отриманої відповіді передбачені спеціальні асинхронні методи об'єкта Response. Найчастіше використовується response.json() для парсингу JSON-даних, але також доступні response.text() для роботи з простим текстом або HTML, та response.blob() для скачування бінарних файлів, зображень чи аудіо. Ці методи також повертають проміси, тому для отримання остаточного результату до них необхідно застосовувати оператор await.
Під час роботи з Fetch API розробникам слід пам'ятати про важливу архітектурну особливість: проміс, що повертається функцією fetch, відхиляється (переходить у стан rejected) лише у разі серйозних мережевих збоїв, наприклад, за повної відсутності підключення до інтернету або помилок DNS. Якщо ж сервер відповідає кодами помилок 404 (Не знайдено) або 500 (Внутрішня помилка сервера), проміс усе одно вважатиметься успішним. Саме тому після виклику fetch завжди необхідно перевіряти прапорець response.ok або самостійно аналізувати властивість response.status.
QUESTION: Як зробити POST запит fetch?
Для надсилання даних на сервер із використанням сучасного Fetch API необхідно передати у функцію fetch другий аргумент — об'єкт конфігурації, що містить метод, заголовки та тіло запиту. Стандартний POST-запит виглядає наступним чином: fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }). такий підхід дозволяє передавати структуровані дані, попередньо серіалізовані у формат JSON за допомогою методу JSON.stringify.
Вкрай важливо правильно вказувати заголовок Content-Type: application/json в об'єкті headers, щоб сервер на бекенді розумів, у якому форматі передається тіло запиту і міг коректно його розпарсити. Якщо ж вашою метою є завантаження файлів на сервер, наприклад, картинок чи документів, замість JSON використовується об'єкт FormData. У такому разі в body передається екземпляр FormData з доданими файлами, а браузер автоматично встановлює правильний заголовок multipart/form-data разом із граничною міткою (boundary).
Після надсилання POST-запиту ви отримуєте проміс з об'єктом відповіді, з якого можна витягти дані за допомогою конструкцій на кшталт await response.json(). Проте перед цим настійно рекомендується перевіряти успішність операції через властивість response.ok або умову response.status. Пам'ятайте, що fetch не вважає статус 400 чи 500 помилкою промісу, тому ручна перевірка кодів відповіді вбереже ваш додаток від несподіваних збоїв під час обробки некоректних даних на сервері.
QUESTION: Як скасувати fetch запит?
Сучасні веб-додатки часто стикаються з необхідністю скасування мережевих запитів, які стали неактуальними. Наприклад, користувач вводить текст у рядок пошуку, і для кожного введеного символу надсилається запит на сервер. Попередні запити при цьому втрачають актуальність, але продовжують навантажувати мережу та сервер. Для вирішення цього завдання використовується вбудований у сучасні браузери стандартний механізм AbortController.
Першим кроком для реалізації скасування запиту є створення екземпляра цього класу. Ви оголошуєте константу, наприклад, const controller = new AbortController(). Цей об'єкт керує процесом скасування і містить у собі спеціальний сигнал.
Другим кроком цей сигнал передається в параметри функції fetch. Ви викликаєте fetch(url, { signal: controller.signal }), пов'язуючи таким чином мережевий запит із створеним контролером. Тепер браузер знає, що цей запит перебуває під управлінням даного контролера.
Коли виникає необхідність скасувати операцію, наприклад, при знищенні компонента React або запуску нового пошукового запиту, ви викликаєте метод controller.abort(). Ця дія миттєво перериває виконання мережевого запиту на рівні браузера.
Важливим аспектом є обробка цієї події в коді. При скасуванні запиту fetch викидає виняток типу DOMException з іменем AbortError. Щоб додаток не падав з необробленою помилкою, код слід обернути в конструкцію try/catch або використовувати ланцюжок промісів .catch(), де обов'язково потрібно перевіряти тип помилки. Якщо помилка є екземпляром DOMException і її ім'я дорівнює AbortError, ви можете просто проігнорувати її, оскільки скасування було запланованим. Такий підхід дозволяє суттєво економити трафік користувача та підвищувати загальну чуйність інтерфейсу.
QUESTION: Що таке Web Workers?
JavaScript є однопотоковою мовою програмування, що означає виконання всього коду в одному головному потоці. Якщо виконати складне математичне обчислення або обробку великих обсягів даних, інтерфейс додатку зависне і перестане реагувати на дії користувача. Для вирішення цієї проблеми були створені Web Workers, які дозволяють виконувати JavaScript-код у фоновому окремому потоці.
Головна особливість технології полягає в ізоляції. Потоки воркерів виконуються паралельно з основним потоком користувацького інтерфейсу, але вони не мають абсолютно жодного доступу до DOM-дерева, об'єктів window або document. Спроба звернутися до елементів сторінки з воркера призведе до помилки.
Створення фонового скрипта відбувається дуже просто. Ви інстанціюєте об'єкт за допомогою конструкції new Worker('worker.js'), де передаєте шлях до файлу зі скриптом, який виконуватиметься у фоновому режимі. Після цього між головним потоком і воркером налаштовується асинхронний обмін даними.
Для надсилання повідомлень з головного потоку у воркер використовується метод postMessage. Всередині самого файлу worker.js дані приймаються через глобальну подію onmessage. Зворотний зв'язок працює точно так же: воркер може надіслати результат своєї роботи в основний потік за допомогою свого власного postMessage, а головний потік перехопить його через обробник onmessage у об'єкта worker.
Web Workers ідеально підходять для ресурсоємних завдань, таких як фільтрація великих масивів даних, обробка зображень, шифрування чи стиснення файлів. Вивантаження важких обчислень у фонові потоки гарантує, що користувацький інтерфейс залишиться плавним і чуйним за будь-яких навантажень.
QUESTION: Що таке Service Workers?
Service Workers є спеціалізованими скриптами, які браузер запускає у фоновому режимі, окремо від веб-сторінки. Вони діють як програмований мережевий проксі, перехоплюючи та обробляючи всі мережеві запити, які надсилає ваш веб-додаток.
Одна з найголовніших переваг цієї технології — це можливість забезпечити повноцінну офлайн-функціональність. Завдяки інтеграції з інтерфейсом кешування Cache API, Service Worker може зберігати HTML-сторінки, стилі, скрипти та зображення локально на пристрої користувача. Коли користувач втрачає інтернет-з'єднання, додаток все одно продовжує завантажуватися і працювати за рахунок відданих з кешу ресурсів.
Окрім кешування, Service Workers підтримують технологію фонової синхронізації та отримання Push-повідомлень. Навіть якщо вкладка з вашим сайтом повністю закрита, браузер може отримати push-повідомлення від сервера, обробити його силами воркера і показати системне сповіщення користувачу, що суттєво підвищує залученість аудиторії.
Важливо зазначити жорсткі вимоги безпеки, що пред'являються до цієї технології. Через потужні можливості з перехоплення трафіку та зміни відповідей мережі, Service Workers працюють виключно на безпечних протоколах HTTPS (виняток становлять лише локальні середовища розробки на кшталт localhost).
Процес роботи з ними включає реєстрацію скрипта в основному коді додатка, а потім етап встановлення та активації. Під час встановлення зазвичай відбувається попереднє кешування ключових ресурсів, а надалі воркер перехоплює подію fetch, перевіряючи наявність даних у кеші перш ніж надсилати запит у реальну мережу.
QUESTION: Що таке WebSocket?
Технологія WebSocket кардинально відрізняється від класичної моделі запит-відповідь протоколу HTTP. Замість того щоб постійно відкривати нове з'єднання для кожної дії, WebSocket встановлює постійне, повнодуплексне з'єднання між клієнтом і сервером поверх одного єдиного TCP-з'єднання.
Для створення такого з'єднання в JavaScript використовується вбудований клас. Ви просто ініціалізуєте його викликом const socket = new WebSocket('ws://example.com/socket'), після чого браузер ініціює процедуру рукостискання для переходу на протокол WebSocket.
Управління життєвим циклом з'єднання будується на подіях. У об'єкта WebSocket є кілька ключових обробників. Подія onopen спрацьовує одразу після успішного встановлення з'єднання. Подія onmessage активується щоразу, коли від сервера надходять нові дані. Подія onerror фіксує помилки з'єднання, а onclose повідомляє про те, що канал було закрито.
Для надсилання текстових або бінарних даних на сервер використовується метод send(), який викликається безпосередньо на об'єкті сокета. Сервер може в будь-який момент часу надіслати повідомлення клієнту без попереднього запиту з його боку.
Головною перевагою WebSockets є миттєва швидкість передачі даних та мінімальний мережевий оверхед, оскільки заголовки повідомлень значно менші за HTTP-заголовки. Ця технологія незамінна при розробці інтерактивних додатків реального часу, таких як чати, онлайн-ігри, фінансові біржові графіки, системи спільного редагування документів та спортивні трансляції.
QUESTION: Що таке queueMicrotask?
Асинхронна модель JavaScript спирається на концепцію циклу подій та черг завдань. Функція queueMicrotask є вбудованим у платформу механізмом, який дозволяє явно додавати нове мікрозавдання у спеціальну чергу мікрозавдань рушія JavaScript.
Коли ви викликаєте queueMicrotask(() => {}), передана анонімна функція не виконується моментально. Вона потрапляє в чергу мікрозавдань і гарантовано виконається одразу після того, як завершиться виконання поточного синхронного блоку коду, але перед тим, як управління буде передано іншим типам завдань або відмальовуванню інтерфейсу.
Головна особливість мікрозавдань полягає в їх найвищому пріоритеті всередині циклу подій. Наприклад, черги макрозавдань, такі як setTimeout або setInterval, мають значно менший пріоритет. Рушій завжди спочатку спустошить усю чергу мікрозавдань до кінця, і лише потім перейде до виконання наступного макрозавдання.
Функція queueMicrotask концептуально працює точно так само, як створення і розв'язання промісу через конструкцію на кшталт Promise.resolve().then(() => {}). Проте використання queueMicrotask є більш чистим, нативним і читабельним способом планування мікрозавдань, коли вам не потрібно створювати сам об'єкт промісу заради виконання відкладеного коду.
Цей інструмент часто використовується бібліотеками та фреймворками для пакетної обробки оновлень стану, відкладеного очищення ресурсів або гарантії того, що певний код виконається асинхронно, але якомога раніше, без затримок, характерних для таймерів.
[ITEM 1] QUESTION: Що таке прототипне наслідування?
Прототипне наслідування є фундаментальною концепцією мови JavaScript, яка визначає механізм взаємодії та розширення об'єктів. На відміну від класичних мов програмування, де структури даних будуються на базі жорстких креслень-класів, JavaScript використовує концепцію об'єктів, які можуть безпосередньо посилатися один на одного. Кожен об'єкт у JavaScript має внутрішню приховану властивість, яка позначається як [[Prototype]] і вказує на інший об'єкт або має значення null.
Коли ви намагаєтеся звернутися до якоїсь властивості або методу об'єкта, інтерпретатор спочатку шукає її в самому об'єкті. Якщо шукана властивість не знайдена, пошук автоматично триває ланцюжком прототипів вгору до батьківського об'єкта, потім до пращура і так далі, поки властивість не буде виявлена або ланцюжок не завершиться. Для програмного отримання цього посилання використовується вбудована функція Object.getPrototypeOf(), яка повертає прототип вказаного об'єкта.
Якщо ж вам необхідно створити новий об'єкт із конкретним прототипом, чудово підійде метод Object.create(), що дозволяє гнучко налаштувати початкові зв'язки між сутностями без використання громіздких конструкцій. Історично склалося і досі підтримується властивість __proto__, яка виступає в ролі гетера і сетера для прихованого [[Prototype]], хоча на практиці для зміни прототипів вже існуючого об'єкта рекомендується використовувати більш сучасні та продуктивні підходи, такі як Object.setPrototypeOf(), або одразу задавати їх при створенні через Object.create().
Розуміння цього механізму дозволяє розробникам ефективно організовувати перейменування та перевикористання коду, будувати гнучкі архітектури та оптимізувати споживання пам'яті в додатках. Як практичний приклад можна уявити створення базового об'єкта з методами логування, від якого успадковуватимуться всі інші бізнес-сутності проєкту. Це позбавляє від дублювання коду і дозволяє в будь-який момент змінити поведінку батьківського об'єкта, що автоматично відобразиться на всіх його нащадках.
[ITEM 2] QUESTION: Як працюють класи ES6?
Класи в стандартах ES6 і вище є потужним інструментом структурування коду, проте під капотом вони влаштовані гранично просто. Основна їхня суть полягає в тому, що синтаксис класів є зручним синтаксичним цукром над звичним прототипним наслідування JavaScript, позбавляючи розробників необхідності вручну писати функції-конструктори та налаштовувати prototype ланцюжки. Базове визначення класу виглядає так: ми використовуємо ключове слово class, за яким слідує ім'я, а всередині тіла описується спеціальний метод constructor() {}, який автоматично викликається при створенні нового екземпляра через оператор new і служить для ініціалізації початкового стану об'єкта.
Для реалізації ієрархії та розширення можливостей існуючих сутностей використовується механізм наслідування за допомогою ключового слова extends, що дозволяє дочірньому класу переймати функціонал батьківського. При цьому в конструкторі дочірнього класу обов'язково потрібно викликати функцію super(), яка передає керування конструктору батька та ініціалізує контекст this. На практиці це виглядає так: створюється клас Animal з базовими властивостями на кшталт імені, а потім клас Dog extends Animal, який додає специфічні методи для собаки.
Важливою особливістю реалізації класів у JavaScript є те, що всі методи, оголошені всередині тіла класу, автоматично записуються в прототип функції-конструктора, тобто в prototype. Це забезпечує суттєву економію оперативної пам'яті, оскільки екземпляри не дублюють методи у себе, а поділяють їх через спільний ланцюжок прототипів. Кожен створений об'єкт зберігає лише власні унікальні властивості, а методи запозичує у загального батьківського шаблону.
Таким чином, класи поєднують у собі суворий і зрозумілий синтаксис, знайомий програмістам з інших мов, зберігаючи при цьому всю гнучкість і легковесну природу прототипної моделі JavaScript. Використання класів робить кодову базу більш читабельною та підтримуваною, особливо у великих командних проєктах, де стандартизація написання компонентів відіграє ключову роль.
[ITEM 3] QUESTION: Що таке static у класах?
Ключове слово static у класах JavaScript відкриває можливості для створення статичних властивостей і методів, які належать не конкретному екземпляру об'єкта, а самому класу загалом. Оголошення статического методу виглядає як static method() {}, а статичне поле задається через конструкцію виду static property = value. Головна відмінність статичних членів від звичайних полягає в способі їх виклику: до них звертаються виключно через ім'я класу, наприклад ClassName.method(), а не через створений об'єктний екземпляр.
Більше того, спроба викликати статичний метод або прочитати статичну властивість через змінну, що зберігає екземпляр класу, призведе до помилки, оскільки ці сутності просто не потрапляють у прототипний ланцюжок об'єктів. Пам'ять під статичні члени виділяється єдиноразово під час завантаження класу в пам'ять, і вони існують незалежно від того, чи було створено хоча б один додаток або екземпляр цього класу.
На практиці статичні методи і властивості найчастіше застосовуються для реалізації різноманітних утилітних функцій, допоміжних алгоритмів, фабричних методів для створення об'єктів, а також для зберігання констант або загального стану, який має бути єдиним для всіх майбутніх екземплярів даного класу. Наприклад, клас Database може містити статичний метод Database.connect(), який повертає єдине підключення до бази даних за паттерном Сінглтон.
Використання static дозволяє логічно ізолювати логіку, яка не залежить від даних конкретного об'єкта, роблячи код чистішим, структурованішим та професійнішим. Розробники можуть легко групувати допоміжні інструменти прямо всередині бізнес-класів, не створюючи для цього окремі файли з розрізненими функціями.
[ITEM 4] QUESTION: Що таке getter і setter?
Гетери і сетери у JavaScript є спеціальними методами об'єктів і класів, які дозволяють керувати доступом до властивостей, маскуючи виклики функцій під звичайне присвоєння або читання значень. Гетер оголошується за допомогою ключового слова get перед ім'ям властивості, наприклад get propertyName() { return this._value; }, а сетер задається через ключове слово set, наприклад set propertyName(val) { this._value = val; }. Ключова особливість цих конструкцій полягає в тому, що вони викликаються як звичайні властивості через точкову нотацію, без використання круглих дужок, що робить інтерфейс роботи з об'єктом максимально прозорим і природним для розробника.
На практиці гетери та сетери незамінні тоді, коли вам потрібно реалізувати складну валідацію вхідних даних перед їх записом у приховане поле. Наприклад, сетер віку може перевіряти, чи є передане число додатним, і викидати помилку в іншому випадку, захищаючи об'єкт від некоректного стану. Гетери в свою чергу чудово підходять для автоматичного перерахунку обчислюваних властивостей, що залежать від інших параметрів, на льоту, позбавляючи необхідності синхронізувати дані вручну.
Крім валідації та обчислень, цей механізм активно використовується для логування будь-яких спроб читання чи модифікації внутрішнього стану об'єкта, що вкрай корисно під час налагодження складних систем. Ви можете легко відстежувати, в який момент часу і яке значення було присвоєно певній властивості, додавши в сетер відповідні виведення в консоль.
Крім того, цей механізм застосовний не тільки в класах, а й у звичайних літералах об'єктів, що дозволяє легко впроваджувати інкапсуляцію та реактивну поведінку в найрізноманітніші структури даних вашого додатка без написання надлишкового бойлерплейту. Це робить код надійнішим, гнучкішим і стійкішим до несподіваних змін з боку інших модулів програми.
[ITEM 5] QUESTION: Що таке приватні поля класів?
Приватні поля класів, що з'явилися в стандарті ES2022+, кардинально змінили підхід до інкапсуляції даних у JavaScript, дозволивши розробникам створювати дійсно приховані від зовнішнього світу властивості та методи. Для того щоб зробити поле або метод приватним, достатньо додати перед його ім'ям символ решітки, що виглядає як #privateField для властивостей і #privateMethod() {} для методів. Головна гарантія безпеки таких членів полягає в тому, що вони доступні виключно всередині тіла самого класу, де були оголошені, і повністю ізольовані від зовнішнього світу.
Якщо спробувати звернутися до приватного поля або викликати приватний метод назовні класу через екземпляр або з функцій-нащадків, інтерпретатор JavaScript негайно викине синтаксичну або рантайм-помилку, запобігаючи несанкціонованому доступу до внутрішньої логіки. Це нововведення вирішило давню проблему JavaScript, в якому довгий час доводилося використовувати різні угоди про іменування, на кшталт додавання нижнього підкреслення перед ім'ям властивості, або складні замикання для приховування даних.
На практиці приватні поля ідеально підходять для зберігання внутрішніх токенів авторизації, кешу запитів, допоміжних лічильників або проміжних результатів обчислень, які не повинні хвилювати користувача цього класу. Наприклад, у класі User ви можете створити приватне поле #passwordHash, яке заповнюватиметься при реєстрації та використовуватися тільки всередині методу верифікації, залишаючись недоступним для читання з глобальної області видимості.
Тепер нативна підтримка приватних полів на рівні мови дозволяє писати надійний, безпечний і захищений від випадкових модифікацій код, чітко поділяючи публічний інтерфейс класу та його внутрішню реалізацію. Це піднімає архітектуру клієнтських і серверних додатків на абсолютно новий рівень надійності та безпеки.
QUESTION: Як працює успадкування класів?
Успадкування класів у сучасному JavaScript побудоване на базі синтаксичного цукру над прототипним успадкуванням і реалізується за допомогою ключового слова extends. Щоб дочірній клас міг успішно успадковуватися від батьківського, використовується конструкція виду class Child extends Parent {}. Водночас створення екземпляра дочірнього класу вимагає суворого дотримання правил роботи з конструктором. Якщо в класі-нащадку визначається власний конструктор, виклик super() у ньому є обов'язковим і повинен стояти на найпершому рядку до звернення до ключового слова this. Це необхідно для того, щоб коректно ініціалізувати контекст батьківського класу. Якщо пропустити виклик super(), інтерпретатор видасть помилку.
Окрім ініціалізації, оператор super активно застосовується для виклику батьківських методів із дочірнього класу. Наприклад, якщо в дочірньому класі ви хочете розширити функціональність методу, який уже існує в батька, ви можете написати super.method() усередині вашого перевизначеного методу, щоб спочатку виконати логіку батька, а потім додати власну. Перевизначення методів дає змогу гнучко налаштовувати поведінку об'єктів під конкретні вимоги завдання.
Для перевірки належності об'єкта до певного класу або функції-конструктора використовується оператор instanceof. Він дає змогу безпечно визначити, чи був створений об'єкт на основі конкретного класу, що особливо корисно під час роботи з ієрархіями класів і поліморфізмом.
QUESTION: Що таке new.target?
Спеціальна властивість new.target, що з'явилася в стандарті ES6, є потужним інструментом для метапрограмування в JavaScript. Головне завдання цієї властивості — вказувати безпосередньо на той конструктор, який був викликаний із використанням ключового слова new. Якщо функцію або клас було викликано звичайним чином, без оператора new, то значення new.target дорівнюватиме undefined. Ця особливість дає змогу легко визначати, чи був викликаний конструктор коректно, і за потреби примусово викликати його через new або викидати помилку.
Одним із класичних практичних застосувань new.target є реалізація абстрактних класів, які напряму не повинні мати екземплярів. Ви можете написати всередині конструктора базового класу перевірку: якщо new.target дорівнює самому абстрактному класу, слід ініціювати помилку, яка забороняє створення об'єкта напряму, дозволяючи створення екземплярів лише через дочірні класи-нащадки.
Важливо враховувати особливості поведінки new.target усередині різних типів функцій. У звичайних функціях і конструкторах класів він посилається на конструктор, що викликав. Однак у стрілочних функціях власної властивості new.target не існує. Усередині стрілочної функції вона береться із зовнішнього контексту, так само, як і ключове слово this, що робить поведінку передбачуваною під час використання вкладених функцій.
QUESTION: Що таке instanceof?
Оператор instanceof в JavaScript слугує для перевірки того, чи входить конкретний об'єкт до ланцюжка прототипів певного класу або функції-конструктора. Синтаксис використання гранично простий і виглядає як obj instanceof Class. Якщо в процесі пошуку ланцюжком прототипів об'єкта obj буде знайдено прототип, що відповідає властивості prototype перевірюваного класу Class, оператор поверне true, в іншому разі — false.
Цей механізм однаково ефективно працює як із сучасними класами, так і з традиційними функціями-конструкторами. Однак слід пам'ятати, що instanceof не призначений для перевірки примітивних типів даних, таких як рядки, числа чи логічні значення. Для примітивів традиційно використовується оператор typeof.
Мова JavaScript також надає можливість кастомізації поведінки оператора instanceof за допомогою спеціального вбудованого символу Symbol.hasInstance. Визначивши цей статичний метод усередині вашого класу, ви можете повністю перевизначити логіку перевірки, зробивши її нестандартною. Це дає розробникам змогу створювати гнучкі бібліотеки та фреймворки, де перевірка належності об'єктів до типів даних може керуватися програмно.
QUESTION: Що таке міксини?
Міксини в JavaScript є патерном проєктування, який використовується як ефективна альтернатива множинному успадкуванню, що відсутнє в мові напряму. Суть міксину полягає в інкапсуляції певного набору методів і властивостей в окремий об'єкт або функцію, які потім можуть бути додані до прототипу цільового класу.
Найпростіший спосіб реалізації міксину — використання методу Object.assign(Class.prototype, mixin). Цей підхід дозволяє швидко скопіювати всі методи з об'єкта міксину прямо в прототип вашого класу, роблячи їх доступними для всіх майбутніх екземплярів. Для складніших сценаріїв застосовуються функції-міксини, які приймають базовий клас як аргумент і повертають новий розширений клас, використовуючи синтаксис extends із динамічним поверненням значення.
Використання міксинів дозволяє будувати архітектуру додатків на основі композиції поведінки, а не жорсткої ієрархії успадкування. Ви можете створювати невеликі незалежні модулі функціональності — наприклад, логування, керування подіями або валідацію — і підключати їх лише до тих класів, яким це дійсно необхідно, уникаючи дублювання коду та створення громіздких дерев успадкування.
QUESTION: Що таке Object.create()?
Метод Object.create() є фундаментальним інструментом в арсеналі JavaScript-розробника для роботи з прототипним успадкуванням. Він дозволяє створювати абсолютно новий об'єкт, явно вказуючи для нього об'єкт-прототип. Базовий синтаксис виклику виглядає як Object.create(proto, descriptors), де першим аргументом передається прототип об'єкта, що створюється, а другим — опціональні дескриптори властивостей.
Однією з найвідоміших і найкорисніших різновидів цього методу є створення об'єкта без прототипу за допомогою виклику Object.create(null). Такий об'єкт не успадковує стандартні методи від Object.prototype, включаючи toString, valueOf та інші. Це робить створений об'єкт ідеально чистим словником або хеш-картою, що виключає будь-які конфлікти імен і проблеми безпеки під час динамічного додавання ключів.
Другий аргумент методу, descriptors, дозволяє детально налаштувати властивості об'єкта, що створюється, за допомогою дескрипторів, задаючи для них параметри перелічуваності, змінюваності та доступності для запису. Це дає повний контроль над структурою даних і дозволяє реалізовувати інкапсуляцію на рівні властивостей об'єкта.
QUESTION: Що таке Object.defineProperty?
Метод Object.defineProperty в JavaScript є потужним інструментом низькорівневого керування властивостями об'єктів, що дозволяє точно налаштовувати їхню поведінку. Він визначає нову властивість безпосередньо на об'єкті або змінює вже наявну, повертаючи цей самий об'єкт. Головна особливість методу полягає в можливості використання дескрипторів — спеціальних об'єктів конфігурації, які визначають приховані характеристики властивості.
Існує два типи дескрипторів: дескриптори даних і дескриптори доступу. Дескриптор даних містить такі параметри, як value, що визначає значення властивості, та writable, який керує можливістю зміни цього значення. Дескриптор доступу замість value використовує функції-аксесори get і set для перехоплення читання та запису властивості, що зручно для обчислень «на льоту» або валідації.
Важливим аспектом роботи методу є те, що за замовчуванням усі прапорці керування (writable, enumerable, configurable) для нових властивостей, створених через Object.defineProperty, встановлюються в значення false. Це означає, що властивість за замовчуванням не перезаписуватиметься, не братиме участі в циклах на кшталт for...in і її не можна буде видалити чи змінити її дескриптори надалі. Для перевірки поточних налаштувань конкретної властивості можна використовувати парний метод Object.getOwnPropertyDescriptor.
Практичне застосування Object.defineProperty включає створення незмінних констант усередині об'єктів, приховування службових полів від ітерації або створення динамічних властивостей, що обчислюються під час кожного звернення. Наприклад, ви можете визначити властивість fullName, яка автоматично збиратиметься з firstName і lastName під час читання, та розбиватиме рядок назад під час запису.
[ITEM 2] QUESTION: Що таке Proxy?
Об'єкт Proxy в сучасному JavaScript є спеціальною обгорткою навколо іншого об'єкта, яка дозволяє перехоплювати та перевизначати базові операції з ним, такі як читання властивостей, запис, виклик функцій та багато іншого. Синтаксис створення проксі виглядає як new Proxy(target, handler), де target — це оригінальний об'єкт, а handler — об'єкт, що містить функції-перехоплювачі, які називають пастками або traps.
Серед найпопулярніших пасток можна виділити get для перехоплення читання властивостей, set для перехоплення запису, apply для перехоплення виклику функцій та construct для перехоплення створення екземплярів через оператор new. завдяки цьому Proxy відкриває величезні можливості для метапрограмування, створення кастомної поведінки та вирішення архітектурних завдань, які раніше були неможливими або вимагали складних милиць.
На практиці Proxy активно використовується для реалізації реактивності в сучасних фреймворках, коли зміна стану об'єкта автоматично запускає перемальовування користувацького інтерфейсу. Також проксі незамінний для валідації даних «на льоту»: наприклад, можна створити пастку set, яка перевірятиме, щоб властивість age, що присвоюється, завжди була додатним числом, і викидатиме помилку в іншому випадку. Інші сценарії включають логування доступу до властивостей, віртуалізацію великих масивів або створення об'єктів із захистом від доступу до неіснуючих полів.
Під час написання пасток часто виникає необхідність виконати стандартну дію оригінального об'єкта. Для цього використовується вбудований об'єкт Reflect, методи якого повністю дублюють пастки Proxy. Використання Reflect усередині handler гарантує коректний контекст виконання this та правильну обробку внутрішніх механізмів JavaScript.
[ITEM 3] QUESTION: Що таке Reflect?
Вбудований об'єкт Reflect у JavaScript є глобальним об'єктом, який надає набір статичних методів для виконання перехоплюваних операцій над об'єктами. Концептуально методи Reflect дзеркально повторюють пастки об'єкта Proxy і служать для того, щоб спростити виконання стандартних операцій на кшталт читання властивостей, їх запису, перевірки наявності чи видалення.
Серед ключових методів Reflect можна виділити Reflect.get, Reflect.set, Reflect.has та Reflect.deleteProperty. Головна відмінність методів Reflect від старих операторів або методів Object полягає в типі значення, що повертається. Наприклад, оператор delete або метод Object.defineProperty у разі невдалої операції можуть викинути виняток або повернути неінтуїтивний результат, тоді як методи Reflect завжди повертають логічне значення boolean, що вказує на успішність чи неуспіх операції.
Reflect тісно пов'язаний з об'єктом Proxy і створювався багато в чому для спільного використання з ним. Коли ви перехоплюєте операцію в Proxy-обробнику, стандартною практикою є делегування цієї операції оригінальному об'єкту за допомогою відповідного методу Reflect. Це забезпечує передбачувану поведінку коду, правильну передачу контексту та сумісність із внутрішніми слотами об'єктів.
Окрім роботи з Proxy, Reflect пропонує більш явний, функціональний та уніфікований API для повсякденної роботи з метаданими об'єктів. Наприклад, перевірка наявності властивості через Reflect.has(target, propertyKey) виглядає чистішою та сучаснішою, ніж використання оператора 'in' у деяких складних патернах проєктування, роблячи код більш читабельним і легкопідтримуваним у межах великих командних проєктів.
[ITEM 4] QUESTION: Як зробити об'єкт незмінним?
Забезпечення незмінності даних — часте завдання в розробці на JavaScript, особливо під час роботи з парадигмами функціонального програмування або керуванням станом. У мові передбачено кілька рівнів захисту об'єктів від змін, кожен із яких вирішує свої завдання з різним ступенем суворості.
Найрадикальнішим методом є Object.freeze(). Цей метод здійснює повне заморожування об'єкта: у нього не можна додавати нові властивості, видаляти наявні, а також змінювати їхні значення чи дескриптори (writable і configurable встановлюються в false). Однак важливо пам'ятати, що заморожування є поверхневим, тобто якщо властивість об'єкта сама є вкладеним об'єктом, то внутрішній об'єкт залишиться змінним, якщо не застосувати до нього заморожування рекурсивно.
Проміжний рівень захисту надає метод Object.seal(). Він робить об'єкт запечатаним: забороняє додавання нових властивостей і видалення наявних, але залишає можливість змінювати значення вже наявних властивостей, якщо їхній прапорець writable дорівнює true. Ще м'якший варіант — Object.preventExtensions(), який забороняє лише додавання нових властивостей, залишаючи все інше без змін.
Для перевірки поточного стану захищеності об'єктів у JavaScript передбачені спеціальні булеві методи: Object.isFrozen(), Object.isSealed() та Object.isExtensible(). Розуміння різниці між цими методами дозволяє розробникам вибирати оптимальний рівень захисту даних залежно від архітектурних вимог додатка, запобігаючи випадковим мутаціям стану в складних логічних ланцюжках.
[ITEM 5] QUESTION: Що таке Object.entries/keys/values?
Методи Object.keys(), Object.values() та Object.entries() є стандартним і зручним набором інструментів для трансформації об'єктів у масиви та подальшої їх ітерації. Кожен із цих статичних методів вирішує своє специфічне завдання з вилучення даних зі структури ключ-значення.
Метод Object.keys() приймає об'єкт як аргумент і повертає масив рядків, що містить усі імена власних ключів цього об'єкта. Метод Object.values(), навпаки, повертає масив значень, що відповідають цим ключам. Метод Object.entries() об'єднує обидва підходи, повертаючи двовимірний масив пар, де кожен елемент є масивом виду [ключ, значення], що ідеально підходить для циклів на кшталт for...of або передачі в методи масивів.
Важливою особливістю всіх трьох методів є те, що вони працюють виключно з власними перелічуваними властивостями об'єкта (enumerable own properties). Це означає, що властивості, успадковані за ланцюжком прототипів, а також властивості з прапорцем enumerable, встановленим у false, будуть автоматично проігноровані та не потраплять у результуючі масиви.
Для зворотньої трансформації в JavaScript передбачено метод Object.fromEntries(). Він виконує протилежну операцію, приймаючи масив пар [ключ, значення] (або будь-який інший ітерований об'єкт із таким самим форматом) і збираючи з них повноцінний об'єкт. Це тріо методів у поєднанні з методами масивів на кшталт map, filter і reduce утворює потужний пайплайн для декларативної обробки та трансформації даних будь-якої складності.
Як вибрати елементи DOM?
Вибір елементів DOM у JavaScript — це фундаментальний насків, із якого починається будь-яка взаємодія скрипта зі сторінкою. Сучасний стандарт пропонує кілька потужних і гнучких методів для пошуку потрібних вузлів, кожен із яких має свої особливості застосування та продуктивність.
Найшвидшим способом знайти унікальний елемент на сторінці є метод getElementById. Він приймає як аргумент рядок із унікальним ідентифікатором елемента (атрибутом id) та повертає об'єкт цього елемента або null, якщо нічого не знайдено. Оскільки ID має бути унікальним у межах документа, цей метод працює дуже ефективно.
Для складніших і універсальніших завдань використовуються методи querySelector і querySelectorAll. Перший із них, querySelector, приймає будь-який валідний CSS-селектор і повертає перший-ліпший елемент, який задовольняє цю умову. Наприклад, можна шукати за класом, тегом, атрибутом або їх комбінацією. Якщо вам потрібно отримати всі елементи, що відповідають умові, використовується querySelectorAll. Цей метод повертає статичний список елементів у форматі NodeList, яким можна ітеративно пройтися, наприклад, за допомогою циклу forEach.
Історичні методи пошуку, такі як getElementsByClassName та getElementsByTagName, повертають живу колекцію HTMLCollection. Вони шукають елементи за конкретним класом або тегом відповідно. Особливість "живих" колекцій полягає в тому, що вони автоматично оновлюються в реальному часі при зміні структури DOM, що буває зручно в певних сценаріях, але вимагає обережності під час маніпуляцій з елементами всередині циклів.
На практиці вибір методу залежить від вашого завдання. Для точкового пошуку унікальних елементів найкраще підходять getElementById або querySelector. Коли потрібна стилізація або пакетна обробка групи елементів за складними правилами верстки, незамінними виявляться querySelectorAll і селектори CSS.
Як змінити вміст елемента?
Зміна вмісту елементів у DOM — це одне з найчастіших завдань під час розробки інтерактивних вебдодатків. JavaScript надає кілька різних властивостей для цієї мети, кожна з яких вирішує певні завдання та володіє своїми нюансами безпеки й продуктивності.
Найбільш безпечним і рекомендованим способом керування текстовим вмістом є властивість textContent. Вона встановлює або повертає текстовий вміст вузла та всіх його нащадків. Під час використання textContent увесь переданий текст сприймається саме як рядок, а не як HTML-код. Це означає, що якщо користувач спробує ввести шкідливий скрипт через поле введення, браузер просто відобразить його як текст, а не виконає. такий підхід повністю захищає додаток від XSS-атак (міжсайтового скриптингу), тому для роботи зі звичайним текстом завжди слід віддавати перевагу саме цій властивості.
З іншого боку, властивість innerHTML дозволяє не просто змінювати текст, а й впроваджувати повноцінну HTML-розмітку. З її допомогою можна динамічно створювати складні структури всередині елемента, додаючи нові теги, атрибути та вкладені блоки. однак головна проблема innerHTML полягає в його вразливості до XSS-атак. Якщо ви впроваджуєте в innerHTML дані, отримані від користувачів напряму та без попередньої суворої фільтрації, зловмисник може виконати довільний JavaScript-код у браузері інших користувачів. Тому застосовувати innerHTML варто лише з повністю довіреним статичним або попередньо санітизованим контентом.
Існує також властивість innerText, яка багато в чому схожа на textContent, але враховує стилі CSS і видимість елементів на сторінці. Наприклад, innerText не поверне текст прихованих за допомогою display: none елементів і враховує форматування. Однак його використання обходиться браузеру дорожче з погляду продуктивності, оскільки вимагає перерахунку макета сторінки.
У реальній розробці вибір зводиться до простого правила: для виведення звичайного тексту використовуйте textContent задля максимальної безпеки та швидкості, а для вставки готової розмітки використовуйте innerHTML, попередньо переконавшись у безпеці джерела даних.
Як працювати з атрибутами?
Робота з атрибутами HTML-елементів у JavaScript дозволяє динамічно керувати поведінкою, станом і зовнішнім виглядом компонентів сторінки. Для цього існує стандартний набір методів і властивостей, які дають повний контроль над будь-якими стандартними та кастомними атрибутами.
Базові маніпуляції виконуються за допомогою методів getAttribute() та setAttribute(). Метод getAttribute(name) приймає ім'я атрибута у вигляді рядка та повертає його поточне значення. Якщо такого атрибута в елемента немає, метод поверне null. Метод setAttribute(name, value) дозволяє встановити нове значення для вказаного атрибута або створити його, якщо він ще не існував. Для видалення непотрібних атрибутів використовується метод removeAttribute(name), який повністю стирає вказаний атрибут із розмітки елемента. Швидко перевірити наявність атрибута можна за допомогою методу hasAttribute(name), який повертає булеве значення true або false.
Особливий інтерес становлять кастомні атрибути, які в сучасному HTML прийнято називати через префікс data- (наприклад, data-user-id, data-role). Для зручної роботи з ними в JavaScript передбачено спеціальну властивість dataset. Вона автоматично перетворює всі data- атрибути елемента на об'єкт із camelCase-нотацією. наприклад, якщо у вас є атрибут data-user-id, ви можете отримати або змінити його значення напряму через властивість el.dataset.userId. Це позбавляє від необхідності постійно викликати методи getAttribute та setAttribute, роблячи код чистішим і зрозумілішим.
На практиці ці інструменти застосовуються повсюдно: від передачі ідентифікаторів записів із бази даних в обробники кліків до зберігання стану інтерфейсу прямо в розмітці. Використання dataset є сучасним стандартом для кастомних даних, водночас класичні методи getAttribute та setAttribute є незамінними під час роботи зі стандартними атрибутами на кшталт href, src, disabled або aria-міток.
Як працювати з класами елемента?
Керування класами CSS у JavaScript є ключовим інструментом для створення динамічних користувацьких інтерфейсів, анімацій та зміни тем оформлення. Найсучаснішим, зручним та ефективним способом роботи з класами виступає властивість classList, яка являє собою спеціальний об'єкт DOMTokenList.
Об'єкт classList надає багатий набір методів для точкової зміни класів елемента. Метод classList.add('className') додає один або кілька нових класів до елемента, якщо їх там ще немає. Метод classList.remove('className') видаляє вказаний клас, дозволяючи легко приховувати елементи або скидати стани. Універсальний метод classList.toggle('className') працює як перемикач: якщо клас у елемента є — він видаляється, якщо немає — додається. Це вкрай зручно для реалізації випадаючих меню, модальних вікон або перемикання тем.
Для перевірки наявності конкретного класу використовується метод classList.contains('className'), що повертає логічне true або false, що є незамінним в умовних конструкціях. А метод classList.replace(oldClass, newClass) дозволяє замінити один існуючий клас на новий за один оператор, що позбавляє від необхідності писати комбінації з remove та add.
Історичний підхід передбачав використання властивості className, яка повертає або встановлює весь рядок класів елемента цілком. Працювати з className менш зручно, оскільки при зміні одного класу доводиться вручну парсити та склеювати весь рядок цілком, щоб не затерти решту стилів. На відміну від нього, classList позбавляє від цих проблем, дозволяючи керувати кожним класом ізольовано та безпечно.
Наприклад, щоб реалізувати інтерактивну кнопку "лайк", достатньо повісити на неї обробник подій, який викликатиме el.classList.toggle('is-active'). Це робить код мінімалістичним, зрозумілим і легко підтримуваним у будь-яких проєктах.
Як змінити стилі елемента?
Зміна стилів елементів за допомогою JavaScript дозволяє створювати динамічні та чуйні інтерфейси, що реагують на дії користувача в реальному часі. Існує кілька підходів до керування зовнішнім виглядом елементів, кожен із яких підходить для певного кола завдань.
Найпряміший спосіб змінити окрему властивість стилю — звернутися до властивості style цільового елемента, наприклад: el.style.color = 'red'. Важливо пам'ятати, що імена властивостей CSS, що складаються з кількох слів, у JavaScript пишуться у форматі camelCase замість kebab-case. Так, CSS-властивість background-color перетворюється в JavaScript на backgroundColor. Цей підхід є ідеальним для точкової зміни одного чи двох параметрів, коли логіка інкапсульована прямо в скрипті.
Якщо вам потрібно застосувати одразу кілька стилів одночасно, зручніше використовувати властивість cssText. Вона дозволяє записати цілий рядок CSS-правил прямо в елемент, наприклад: el.style.cssText = 'color: red; background-color: blue; font-size: 16px;'. Однак варто враховувати, що присвоєння значення через cssText повністю перезаписує всі інлайн-стилі, які були задані раніше через властивість style.
Іноді виникає необхідність дізнатися не те, що записано в інлайн-стилях елемента, а його реальні, шукані браузером стилі з урахуванням усіх каскадних таблиць стилів і правил успадкування. Для цього використовується глобальна функція getComputedStyle(el). Вона повертає об'єкт з усіма обчисленими стилями елемента, значення яких можна прочитати, але напряму змінювати через цей об'єкт не можна.
Окремої уваги заслуговує робота з CSS custom properties (змінними). Ви можете динамічно змінювати значення CSS-змінних прямо з JavaScript, звертаючись до методу setProperty на об'єкті style елемента. Наприклад, el.style.setProperty('--main-color', '#ff0000'). Це неймовірно потужний підхід для реалізації тем оформлення, коли вся логіка зовнішнього вигляду винесена в CSS, а скрипт лише перемикає значення змінних.
QUESTION: Как создать и добавить элемент?
Створення та додавання нових елементів у документ є одним із найпоширеніших завдань під час розробки веб-додатків із використанням JavaScript. Цей процес складається з двох основних етапів: безпосереднє створення вузла в пам'яті браузера та його подальша інтеграція в існуюче дерево DOM. Для створення нового вузла використовується метод document.createElement, в який передається рядок з ім'ям тегу, наприклад, document.createElement('div'). Після того як елемент створено, йому можна задати класи, атрибути та текстовий вміст, після чого його необхідно вставити на сторінку.
Для додавання створеного елемента існує кілька методів залежно від того, куди саме його потрібно помістити. Класичним і найбільш універсальним методом є parent.appendChild(el), який додає вузол у самий кінець списку дочірніх елементів вказаного батька. Якщо вам потрібно розмістити елемент перед якимось конкретним уже існуючим дочірнім елементом, використовується метод parent.insertBefore(el, ref), де першим аргументом передається новий елемент, а другим — опорний елемент, перед яким відбудеться вставка.
Сучасний стандарт JavaScript пропонує більш гнучкі та зручні методи для роботи з вузлами. Методи el.append() і prepend() дозволяють додавати елементи всередину батьківського елемента відповідно в самий кінець або на самий початок. Крім того, методи append і prepend приймають не лише вузли, а й звичайні текстові рядки, а також дозволяють додавати одразу кілька елементів за один виклик.
Для ще більш точного позиціонування застосовуються методи insertAdjacentHTML та insertAdjacentElement. Вони дозволяють вставляти розмітку або елементи відносно вибраного елемента в чотирьох точках: перед самим елементом, одразу після його відкриваючого тегу, перед закриваючим тегом або після самого елемента. Правильний вибір методу залежить від архітектури вашого додатка та вимог до продуктивності.
[ITEM 2] QUESTION: Как удалить элемент?
Управління життєвим циклом елементів на веб-сторінці включає не лише їх створення, а й коректне видалення, коли вони більше не потрібні користувачеві. У сучасному JavaScript існує кілька ефективних способів видалення елементів із DOM-дерева, кожен з яких має свої особливості застосування. Найпростішим, лаконічним і сучасним способом є метод el.remove(), який викликається безпосередньо на елементі, що видаляється. Цей підхід підтримується всіма актуальними браузерами і не вимагає звернення до батьківського контейнера, що робить код чистішим і зрозумілішим.
Історично склався інший підхід, який часто зустрічається в старих кодових базах — це метод parent.removeChild(el). Для його використання необхідно спочатку знайти батьківський елемент вузла, що видаляється, а потім передати сам вузол як аргумент. Хоча цей метод вимагає більше рядків коду порівняно з remove(), він може бути корисним у специфічних сценаріях оптимізації або за необхідності суворого контролю ієрархії.
Іноді замість повного видалення елемента потрібно замінити його на інший. Для цієї цілі чудово підходить метод el.replaceWith(newEl), який автоматично замінює поточний елемент на новий вузол або набір вузлів, переданих в аргументах. Якщо ж завдання полягає в швидкому очищенні великого контейнера від усіх дочірніх елементів, часто використовують прийом скидання властивості innerHTML батьківського елемента в порожній рядок, наприклад, innerHTML = ''. Цей спосіб працює дуже швидко, хоча і має нюанси, пов'язані з витоками пам'яті в деяких браузерах через неналежним чином видалені слухачі подій.
Важливо зазначити, що видалення елемента з DOM за допомогою перелічених методів не знищує його об'єкт в оперативній пам'яті остаточно, якщо на нього продовжують посилатися змінні в коді JavaScript. Завдяки цьому видалені елементи можна вставити назад у документ в інше місце за допомогою стандартних методів вставки, зберігши при цьому їхній стан, прив'язані дані та вкладену структуру.
[ITEM 3] QUESTION: Что такое event bubbling и capturing?
Розуміння механізмів поширення подій у браузерному середовищі є критично важливим для ефективної роботи з користувацьким вводом у JavaScript. Коли якась подія, наприклад клік мишкою, відбувається на елементі, браузер запускає процес проходження події по всьому дереву DOM, який складається з двох послідовних фаз: занурення та спливання.
Перша фаза називається capturing або занурення. У цей момент подія починає свій шлях від кореневого об'єкта window вниз через усі проміжні батьківські елементи прямо до цільового елемента, на якому відбулася дія. За замовчуванням слухачі подій на цій фазі не спрацьовують, проте розробник може явно вказати браузеру перехоплювати події на етапі занурення. Для цього під час реєстрації обробника через метод addEventListener як третій аргумент передається логічне значення true або об'єкт із опцією capture: true.
Друга і основна фаза називається bubbling або спливання. Вона настає одразу після того, як подія досягла цільового елемента. На цьому етапі подія починає рухатися у зворотному напрямку — від цільового елемента вгору до його батьків, піднімаючись аж до об'єкта document та window. За замовчуванням усі стандартні події в JavaScript працюють саме в режимі спливання, що дозволяє обробляти дії на вищих рівнях ієрархії.
Для керування цим процесом передбачені спеціальні інструменти. наприклад, метод event.stopPropagation(), викликаний усередині обробника подій, повністю зупиняє подальше поширення події вгору або вниз по дереву DOM, запобігаючи спрацьовуванню аналогічних обробників на батьківських елементах. Це буває необхідно в складних інтерфейсах, коли клік по дочірній кнопці не повинен викликати клік по навколишній її картці.
[ITEM 4] QUESTION: Что такое event delegation?
Делегування подій є одним із найпотужніших та найелегантніших патернів проєктування інтерфейсів у JavaScript. Суть цього підходу полягає в тому, що замість навішування окремого обробника подій на кожен дочірній елемент, ми встановлюємо лише один загальний обробник на їхнього загального батька. Цей метод спирається на механізм спливання подій, завдяки якому клік або будь-яка інша дія на дочірньому вузлі завжди доходить до батьківського контейнера.
При спрацьовуванні такого загального обробника в об'єкті події завжди доступні дві ключові властивості: event.target та event.currentTarget. Властивість event.target вказує на конкретний елемент, на якому безпосередньо відбулася дія, наприклад, на конкретну кнопку всередині списку. Водночас event.currentTarget посилається на елемент, до якого прив'язаний сам обробник, тобто на батьківський контейнер. Різниця між цими властивостями лежить в основі роботи патерну.
Делегування подій стає незамінним інструментом при роботі з динамічним контентом. Якщо список елементів постійно доповнюється новими пунктами з бази даних або користувач додає їх інтерактивно, за звичайного підходу довелося б наново вішати обробники на кожен новий елемент. При делегуванні нові елементи починають працювати автоматично, оскільки їхні події спливають до старого батька.
Оскільки подія спливає від цільового елемента, обробнику на батьку часто потрібно зрозуміти, по якому саме елементу клікнули. Для фільтрації та перевірки елементів використовуються стандартні методи DOM API, такі як matches() та closest(). Метод matches('selector') перевіряє, чи відповідає елемент заданому CSS-селектору, а closest('selector') піднімається вгору від цільового елемента в пошуках найближчого предка, що задовольняє умову, що особливо зручно, якщо клік припав на іконку всередині кнопки.
[ITEM 5] QUESTION: Как предотвратить действие по умолчанию?
Багато елементів веб-сторінки мають вбудовану стандартну поведінку, закладену браузером за замовчуванням. Прикладами такої поведінки є відправка форми при кліку на кнопку submit, перехід за посиланням при кліку на тег a, контекстне меню при натисканні правої кнопки миші або прокручування сторінки при натисканні клавіш зі стрілками. Проте в сучасній веб-розробці часто виникає необхідність перехопити ці дії та опрацювати їх за допомогою скриптів, наприклад, надіслати дані форми через AJAX без перезавантаження сторінки.
Для скасування стандартної поведінки браузера використовується метод event.preventDefault(), який викликається в об'єкта події всередині функції-обробника. Щойно цей метод спрацьовує, браузер припиняє виконання дії за замовчуванням, пов'язаної з поточною подією. Важливо зазначити, що цей метод скасовує саме стандартну поведінку, але не зупиняє поширення події деревом DOM, тому спливання або занурення триватимуть, якщо їх не зупинити окремо.
У старому коді та застарілих стандартах JavaScript для цих же цілей часто використовувалося повернення логічного значення false наприкінці функції-обробника, наприклад, через return false. такий підхід одночасно скасовував дію за замовчуванням і зупиняв спливання події, проте в сучасному коді від нього рекомендується відмовлятися на користь явного виклику event.preventDefault() задля підвищення читабельності та передбачуваності коду.
Для перевірки того, чи було скасовано стандартну поведінку для конкретної події, розробники можуть використовувати спеціальну властивість event.defaultPrevented. Вона повертає логічне значення true, якщо метод preventdefault() уже був викликаний кимось із попередніх обробників. Це корисно у великих комплексних додатках, де кілька незалежних модулів можуть слухати ту саму подію і приймати рішення на основі поточного стану інтерфейсу.
QUESTION: Що таке DOMContentLoaded і load?
Події DOMContentLoaded і load відіграють ключову роль у життєвому циклі завантаження вебсторінки, і розуміння різниці між ними є критично важливим для фронтенд-розробника. Подія DOMContentLoaded спрацьовує на об'єкті document тоді, коли HTML повністю завантажено і розібрано, а дерево DOM повністю побудоване. При цьому браузер не чекає на завантаження стилів, зображень і фреймів, що підвантажуються. Це робить DOMContentLoaded ідеальним моментом для ініціалізації інтерактивних елементів інтерфейсу, навішування обробників подій і виконання базової логіки скриптів, яким не потрібні розміри зображень.
Подія load, зі свого боку, спрацьовує на об'єкті window трохи пізніше, коли повністю завантажено не лише HTML і DOM-дерево, а й усі супутні ресурси: таблиці стилів, скрипти, зображення та сторонні фрейми. Оскільки завантаження важкої графіки може зайняти помітний час, load відбувається значно пізніше, ніж DOMContentLoaded.
На практиці DOMContentLoaded спрацьовує набагато швидше, що дозволяє покращити сприйману користувачем швидкість роботи сайту. Щоб скрипти виконувалися вчасно і не блокували відмальовування сторінки, часто використовують атрибут defer для тегу script, який вказує браузеру завантажувати скрипт асинхронно у фоні та виконувати його одразу після того, як DOMContentLoaded буде завершено.
Для перевірки поточного стану завантаження документа в будь-який момент часу можна використовувати властивість document.readyState. Вона приймає три можливі значення: loading, коли документ ще завантажується; interactive, коли документ повністю прочитано і розібрано (що відповідає моменту перед подією DOMContentLoaded); та complete, коли всі ресурси повністю завантажені (що відповідає моменту перед подією load). Цю властивість зручно використовувати в універсальних функціях, які повинні виконуватися незалежно від того, чи встиг документ завантажитися до підключення скрипта.
[ITEM 2] QUESTION: Як працювати з формами в JS?
Робота з формами в JavaScript є одним із найпоширеніших завдань під час створення інтерактивних вебзастосунків, від простих контактних форм до складних багатокрокових опитування. Для ефективної взаємодії з елементами форми використовується властивість form.elements, яка є живою колекцією всіх полів введення, кнопок і селектів, що містяться всередині форми. Звернутися до конкретного поля можна як за його індексом у колекції, так і за значенням атрибута name, що робить код більш читабельним і надійним при зміні верстки.
Отримати введені користувачем дані або змінити їх можна за допомогою властивості input.value для текстових полів, чекбоксів і радіокнопок. Для перемикачів і чекбоксів також активно використовується властивість checked. Якщо вам потрібно програмно ініціювати надсилання форми без участі користувача, наприклад, після валідації даних або за таймером, застосовується метод form.submit(). Варто враховувати, що програмний виклик submit() не викликає подію submit самої форми, тому всю попередню логіку валідації потрібно виконувати вручну.
Для повернення форми у вихідний стан використовується метод form.reset(), який очищає всі поля введення і повертає їх до значень за замовчуванням, прописаних в HTML. Це зручно використовувати після успішного надсилання даних через AJAX, щоб користувач міг ввести нову інформацію.
Для зручного збору та надсилання даних форми на сервер без перезавантаження сторінки використовується об'єкт FormData. Він автоматично збирає всі значення з елементів форми, у яких задано атрибут name. Приклад типового сценарію роботи виглядає так: користувач заповнює поля, за кліком на кнопку надсилання ви перехоплюєте подію submit, скасовуєте стандартну поведінку браузера через preventDefault(), створюєте екземпляр FormData на основі елемента форми, а потім надсилаєте ці дані на сервер за допомогою асинхронного запиту fetch. такий підхід забезпечує сучасний і плавний користувацький досвід без смикання сторінки.
[ITEM 3] QUESTION: Що таке FormData?
Об'єкт FormData у JavaScript є потужним і зручним інструментом для створення та маніпуляцій з наборами пар ключ-значення, які потім можуть бути легко відправлені на сервер з використанням асинхронних запитів, таких як fetch або XMLHttpRequest. Основний спосіб створення об'єкта FormData — передача в конструктор існуючої HTML-форми: const formData = new FormData(formElement). При цьому об'єкт автоматично зчитує всі поля введення, що мають атрибут name, включаючи текстові поля, випадаючі списки і навіть файли, вибрані користувачем через input типу file.
Окрім автоматичного збору даних з існуючої форми, ви можете працювати з FormData програмно, використовуючи вбудовані методи. Метод append(name, value) дозволяє додавати нові дані або додаткові значення до існуючих ключів. Якщо потрібно отримати конкретне значення за ключем, використовується метод get(name), а для видалення даних служить метод delete(name). Також об'єкт підтримує метод set(name, value), який перезаписує всі існуючі значення із зазначеним ім'ям на нове.
Для обходу всіх даних, що містяться в об'єкті FormData, передбачені спеціальні методи ітерації, такі як entries(). З його допомогою можна легко пройтися по всіх парах ключ-значення за допомогою циклу for...of, що особливо корисно для налагодження або попередньої валідації даних перед відправкою.
Головною перевагою FormData є вбудована підтримка мультимедійних даних. Ви можете додавати файли безпосередньо з input.files, і браузер автоматично сформує запит у форматі multipart/form-data, який коректно опрацьовується бекендом на будь-яких мовах програмування. Відправка таких даних за допомогою fetch виглядає вкрай лаконічно: достатньо передати об'єкт FormData як значення властивості body в конфігураційному об'єкті запиту, не дбаючи про ручне встановлення заголовків Content-Type, так як браузер встановить їх самостійно разом з необхідним унікальним роздільником.
[ITEM 4] QUESTION: Як працювати з localStorage?
Об'єкт localStorage у JavaScript є частиною вебсховища браузера і дозволяє зберігати пари ключ-значення безпосередньо в браузері користувача для персистентного зберігання даних між сесіями. Головна особливість localStorage полягає в тому, що збережені дані залишаються доступними навіть після закриття вкладки, перезапуску браузера або вимкнення комп'ютера, поки користувач або скрипт явно їх не видалять.
Для запису даних у сховище використовується метод localStorage.setItem(key, value), де обидва аргументи обов'язково мають бути рядками. Щоб прочитати збережені дані, застосовується метод localStorage.getItem(key, value), який повертає рядок зі значенням або null, якщо вказаний ключ відсутній у базі. Видалення конкретного елемента здійснюється за допомогою методу localStorage.removeItem(key), а якщо потрібно повністю очистити все сховище для цього домену, використовується метод localStorage.clear(), який видаляє абсолютно всі збережені пари ключ-значення.
Найважливішою технічною особливістю localStorage є те, що він працює виключно з рядками. Якщо ви спробуєте записати в нього об'єкт, масив або булеве значення, JavaScript автоматично перетворить його на рядок "[object Object]", втративши всі вихідні дані. Щоб успішно зберігати складні структури даних, такі як масиви об'єктів або користувацькі налаштування, перед записом необхідно серіалізувати їх у формат JSON за допомогою методу JSON.stringify(data), а при читанні — назад десеріалізувати за допомогою JSON.parse(savedData).
Обсяг пам'яті localStorage зазвичай становить близько 5–10 мегабайт на один домен, чого з надлишком вистачає для текстових налаштувань, токенів авторизації та чорновиків постів. Усі операції зі сховищем відбуваються синхронно в головному потоці виконання, тому не варто зловживати збереженням занадто великих обсягів даних, щоб уникнути можливих затримок у відмальовуванні інтерфейсу.
[ITEM 5] QUESTION: Чим відрізняються localStorage і sessionStorage?
Різниця між localStorage і sessionStorage полягає в тривалості життя і області видимості даних, що зберігаються, хоча обидва ці механізми надають ідентичний API для роботи з вебсховищем браузера. localStorage призначений для довгострокового зберігання даних. Інформація, записана в localStorage, зберігається безстроково — вона не видаляється при закритті вкладки чи браузера і буде доступна під час наступних візитів користувача на сайт доти, доки дані не будуть стерті програмно через код або вручну через налаштування браузера.
sessionStorage працює інакше і орієнтований на тимчасове зберігання даних у межах однієї конкретної вкладки або вікна браузера. Дані в sessionStorage живуть тільки доти, доки відкрита вкладка, в якій вони були створені. Щойно користувач закриває цю вкладку або вікно браузера, вся інформація в sessionStorage безповоротно видаляється. Якщо користувач відкриє ту саму сторінку в новій вкладці, у неї буде зовсім нове і порожнє сховище sessionStorage, навіть якщо домен залишився колишнім.
Обидва сховища мають схожі обмеження щодо обсягу пам'яті, який зазвичай становить від 5 до 10 мегабайт залежно від конкретного браузера. І перше, і друге сховище підтримують однаковий набір методів: setItem, getItem, removeItem, clear, а також працюють виключно з рядками, вимагаючи застосування JSON.stringify і JSON.parse для збереження об'єктів і масивів.
Синхронність операцій характерна для обох типів сховищ: усі запити на читання та запис виконуються в основному потоці JavaScript блокуючим чином. Вибір між ними залежить від бізнес-логіки: localStorage ідеально підходить для збереження користувацьких налаштувань теми, токенів авторизації чи кошика інтернет-магазину, тоді як sessionStorage незамінний для тимчасових даних, таких як стан багатокрокової форми, дані фільтрів поточної сесії або захист від повторного надсилання запитів, які не повинні переноситися між різними вкладками одного сайту.
QUESTION: Що таке cookie в JavaScript?
Поняття cookie в мові програмування JavaScript є механізмом, що дозволяє веб-додаткам зберігати невеликі обсяги текстових даних прямо в браузері користувача. Основним інструментом для роботи з ними виступає спеціальна властивість document.cookie. Важливо розуміти, що документ document.cookie повертає не масив або об'єкт, а єдиний рядок, що містить усі доступні куки поточного домену, розділені крапкою з комою і пробілом, наприклад, ім'я дорівнює значенню. Такий формат вимагає від розробника самостійного парсингу рядка при читанні конкретних параметрів.
Для встановлення або зміни нового значення використовується проста операція присвоєння, де ви задаєте рядок формату document.cookie = 'name=value'. Проте на практиці цього рідко буває достатньо, тому застосовуються додаткові параметри атрибутів, які задаються через крапку з комою. Атрибут expires задає конкретну дату видалення куки у форматі UTC, а max-age визначає час життя куки в секундах з моменту створення, що часто буває зручніше. Параметри path і domain визначають область видимості куки, обмежуючи її конкретним шляхом на сайті або певним піддоменом.
Існують і важливі обмеження безпеки. Так, флаг HttpOnly робить куки недоступними з JavaScript, захищаючи їх від крадіжки через XSS-атаки, оскільки такі куки можуть передаватися тільки сервером у заголовках HTTP. Крім того, накладається жорстке технічне обмеження: розмір однієї куки разом з ім'ям і всіма атрибутами не може перевищувати 4 кілобайти, а загальна кількість кук для одного домену також обмежена браузерами. Прикладом практичного використання може слугувати збереження обраної користувачем темної теми оформлення сайту, яка зчитується при повторному візиті, забезпечуючи персоналізований і комфортний користувацький досвід.
[ITEM 2] QUESTION: Как работать с history API?
Робота з History API в JavaScript дозволяє веб-розробникам програмно керувати історією сеансу браузера, тобто вмістом стека історії поточної вкладки. Це критично важливо для створення сучасних односторінкових додатків, відомих як SPA, де переходи між розділами відбуваються без повної перезагрузки сторінки, але при цьому користувач очікує звичної поведінки інтерфейсу з працюючими кнопками вперед і назад у браузері.
Головним методом для зміни історії без перезагрузки є history.pushState(state, title, url). Цей метод приймає три аргументи: об'єкт стану state, який містить дані, прив'язані до цього стану, заголовок сторінки title, який у більшості сучасних браузерів ігнорується, і нову URL-адресу, яка відображається в адресній рядку браузера. Якщо вам потрібно не додати новий запис, а змінити поточний, використовується метод history.replaceState(), який перезаписує актуальний стан і адресу.
Для навігації історією в коді передбачені методи history.back(), які повертають користувача на одну сторінку назад, history.forward(), що переміщують вперед, а також універсальний метод history.go(delta), де числовий аргумент вказує, на скільки кроків вперед чи назад потрібно зміститися в стеку. Коли користувач натискає кнопки навігації в браузері або викликаються методи go, back, forward, на об'єкті window спрацьовує подія popstate. В обробнику цієї події розробник може отримати актуальний стан через event.state і перемалювати інтерфейс додатка відповідно до розділу, що завантажується.
[ITEM 3] QUESTION: Что такое Intersection Observer?
Інтерфейс Intersection Observer в JavaScript є потужним і ефективним інструментом, призначеним для асинхронного спостереження за перетином цільового елемента з його батьківським контейнером або з областю видимості самого браузера, відомою як viewport. Головна перевага цього підходу полягає в тому, що код виконується не на кожний рух миші або піксель прокручування, а тільки в моменти зміни стану видимості, що колосально знижує навантаження на процесор і підвищує продуктивність веб-сторінки.
Для створення спостерігача використовується конструктор new IntersectionObserver(callback, options), де функція зворотного виклику спрацьовує при кожному перетині, а об'єкт опцій дозволяє налаштувати кореневий елемент, відступи і пороги спрацьовування. Після створення спостерігача необхідно запустити процес стеження за конкретним DOM-вузлом за допомогою методу observer.observe(element), передавши йому потрібний елемент.
Класичними прикладами практичного застосування Intersection Observer є дві поширені задачі: ліниве завантаження зображень і нескінченна прокрутка. У першому випадку картинка починає завантажуватися з сервера тільки тоді, коли користувач наближається до неї під час скролу. У другому випадку спеціальний елемент-маркер у самому низу сторінки запускає підвантаження наступної порції контенту рівно в той момент, коли потрапляє в поле зору користувача, створюючи плавний і безшовний досвід взаємодії.
[ITEM 4] QUESTION: Что такое Mutation Observer?
Інтерфейс Mutation Observer в JavaScript є вбудованим механізмом, який дозволяє асинхронно відстежувати будь-які зміни, що відбуваються в об'єктній моделі документа, тобто в структурі DOM. Цей інструмент прийшов на зміну застарілим мутаційним подіям і працює набагато ефективніше, оскільки збирає всі зміни в пакети і викликає функцію зворотного виклику один раз за цикл відтворення.
Створення спостерігача виконується за допомогою конструктора new MutationObserver(callback), куди передається функція, що приймає масив записів про зміни і сам екземпляр спостерігача. Щоб почати стеження за конкретним вузлом, використовується метод observer.observe(target, config), де target — це цільовий DOM-елемент, а config — об'єкт конфігурації, що визначає типи відстежуваних змін.
В об'єкті конфігурації можна вказати параметри childList для відстеження додавання або видалення дочірніх елементів, attributes для спостереження за зміною атрибутів, а також characterData для відстеження зміни текстового вмісту вузлів. Крім того, якщо потрібно стежити не тільки за прямими нащадками, а й за всією вкладеною структурою дерева елементів всередині цільового вузла, використовується логічний флаг subtree зі значенням true. Це незамінно при створенні складних користувацьких інтерфейсів, плагінів автоматичного перекладу тексту або систем тестування.
[ITEM 5] QUESTION: Что такое Resize Observer?
Інтерфейс Resize Observer в JavaScript є сучасним стандартом для відстеження змін розмірів елементів на веб-сторінці. До появи цього API розробникам доводилося вішати важкі та неефективні обробники на подію зміни розміру вікна window resize, хоча змінюватися міг розмір абсолютно будь-якого блоку незалежно від вікна браузера, наприклад, при відкритті випадаючих меню або динамічному підвантаженні контенту.
Створення спостерігача за розмірами реалізується через конструктор new ResizeObserver(callback). Всередині функції зворотного виклику розробник отримує масив об'єктів змін. Кожен такий об'єкт містить властивість entry.contentRect, всередині якої зберігаються актуальні числові дані про ширину, висоту і координати елемента, причому ці вимірювання враховують внутрішні відступи padding.
Щоб запустити процес спостереження, достатньо викликати метод observer.observe(element, options) і передати йому цільовий DOM-вузол. Цей підхід працює виключно плавно і ефективно, оскільки зміни розмірів обробляються асинхронно в спеціальному циклі візуалізації браузера. Це ідеальне рішення для створення чуйних компонентів інтерфейсу, адаптивних графіків на canvas або складних таблиць, яким потрібно миттєво перебудовувати свою внутрішню верстку при зміні габаритів батьківського контейнера.
QUESTION: Що таке ES модулі?
Сучасна екосистема JavaScript базується на стандартах модульності, серед яких особливе місце займають ES модулі. Це офіційний стандарт для організації та повторного використання коду в JavaScript, який підтримується як сучасними браузерами, так і середовищем Node.js. Головна особливість цього підходу полягає в тому, що кожен файл розглядається як окремий ізольований модуль зі своєю власною областю видимості. Для того щоб поділитися функціоналом з іншими частинами додатка, розробники використовують різні види експорту. Наприклад, іменний експорт дозволяє вивантажити кілька змінних або функцій з одного файлу, використовуючи конструкцію виду export const x = 1, а для експорту за замовчуванням застосовується синтаксис export default fn, який зазвичай використовується для основного компонента або функції модуля.
У свою чергу, для підключення створених модулів в інших частинах програми застосовуються механізми імпорту. Для отримання конкретних іменованих елементів використовується деструктуризація в момент імпорту, наприклад, import {x} from './module'. Якщо ж виникає необхідність імпортувати весь функціонал модуля разом у вигляді єдиного об'єкта, використовується зірковий імпорт: import * as M from './module', після чого всі експорти сутності стають доступними як властивості об'єкта M. Найважливішою характеристикою ES модулів є статичні імпорти. Це означає, що структура залежностей між файлами визначається і аналізується компілятором ще до виконання самого коду, на етапі синтаксичного розбору. Такий підхід дозволяє інструментам збирання та браузерам заздалегідь розуміти, які файли знадобляться для роботи додатка.
Щоб браузер коректно опрацював такі модулі при прямій завантаженні HTML-сторінки, необхідно явно вказати тип скрипта в тезі. Для цього використовується атрибут type='module' в HTML, наприклад, у вигляді конструкції script type='module' src='main.js' /script. завдяки цьому браузер включає режим суворої роботи з модулями, автоматично опрацьовує їх як асинхронні скрипти і дозволяє використовувати всі переваги сучасного синтаксису імпорту та експорту прямо в клієнтській частині вебдодатка без попереднього збирання.
QUESTION: Що таке динамічний імпорт?
Динамічний імпорт у JavaScript є потужним механізмом завантаження модулів у той момент, коли це дійсно необхідно, безпосередньо під час виконання програми. На відміну від статического імпорту, який фіксується на етапі компіляції, динамічний імпорт реалізується у вигляді функції, що приймає шлях до модуля в якості аргументу. Існує два основних синтаксичних підходи для роботи з ним. Перший варіант передбачає використання класичних промісів: import('./module').then(module => { логіка роботи з модулем }). Другий, більш сучасний і зручний спосіб, заснований на синтаксисі async/await, коли код виглядає таким чином: const module = await import('./module'). Такий підхід робить асинхронний код лінійним і більш читабельним, позбавляючи розробника необхідності вибудовувати ланцюжки з методів then.
Головна практична користь динамічного імпорту полягає в реалізації поділу коду на частини, відомого як code splitting. Коли вебдодаток розростається до великих розмірів, відправка всього вихідного коду користувачу одним величезним файлом призводить до серйозних затримок при першому завантаженні сторінки. Динамічний імпорт дозволяє розділити додаток на логічні блоки і завантажувати їх тільки тоді, коли користувач переходить до відповідного розділу або виконує певну дію.
З цим безпосередньо пов'язана концепція лінивого завантаження, або lazy loading. Наприклад, важкі бібліотеки для роботи з графікою, складні модальні вікна або рідкісні розділи адмін-панелі не потрібно завантажувати при старті. Їх можна підвантажити «на льоту» в момент кліка користувача. Оскільки функція динамічного імпорту завжди повертає Promise, додаток може легко обробляти стан завантаження, показувати користувачеві анімацію очікування або спіннер, а в разі мережевої помилки — перехоплювати виняток через блок try/catch або метод catch. Це робить сучасні вебдодатки набагато більш чуйними і продуктивними на будь-яких пристроях, включаючи мобільні телефони з повільним інтернетом.
QUESTION: Чим відрізняються CommonJS та ES модулі?
У світі JavaScript історично склалося співіснування двох основних систем модулів: CommonJS та ES модулів. Розуміння їх відмінностей критично важливе для розробників, оскільки вони мають принципово різну архітектуру та область застосування. Система CommonJS була створена на зорі розвитку Node.js для серверного JavaScript. У ній для експорту даних використовується об'єкт module.exports, а для підключення залежностей застосовується функція require(). Типовий приклад такого коду виглядає як const module = require('./module'). Головна технологічна особливість CommonJS полягає в тому, що вона є синхронною. Це означає, що при виклику функції require файл повністю зчитується і виконується синхронно в момент виклику, що чудово працювало на сервері, де всі файли зберігаються на локальному жорсткому диску.
З іншого боку, ES модулі є сучасним офіційним стандартом мови JavaScript, який використовує ключові слова import та export. На відміну від попередника, ES модулі є асинхронними за своєю природою. Стандарт розроблений з урахуванням особливостей роботи в браузері, де файли модулів можуть завантажуватися мережею з різною затримкою. Тому перед виконанням коду браузер або середовище виконання спочатку аналізують граф залежностей, завантажують усі необхідні модулі і лише потім запускають їх виконання.
Довгий час CommonJS був стандартом виключно для бекенду на Node.js, в той час як ES модулі впроваджувалися в браузери та інструменти збирання. Однак сучасна екосистема зробила крок вперед: сьогодні Node.js успішно підтримує обидва формати. Розробники можуть використовувати ES модулі навіть на сервері, вказавши у файлі package.json поле "type": "module", або використовуючи розширення файлу .mjs для файлів з новим синтаксисом і .cjs для традиційного CommonJS. Тим не менш, міграція індустрії в бік ES модулів триває, оскільки вони надають кращі можливості для статического аналізу коду та оптимізації.
QUESTION: Що таке bundler?
Сучасні вебдодатки складаються з сотень, а іноді й тисяч окремих модулів, файлів стилів, зображень та сторонніх бібліотек. Якщо передавати всі ці файли у вихідному вигляді в браузер користувача, це призведе до сотень HTTP-запитів і критичного падіння продуктивності. Для вирішення цієї проблеми використовується спеціальний інструмент — збиральник, або bundler. Його головне завдання полягає в тому, щоб об'єднувати розрізнені модулі в один або кілька оптимізованих файлів, готових до продуктивної роботи в браузері. Популярними прикладами таких інструментів на сьогоднішній день є Webpack, Vite, Rollup та сучасний надшвидкий збиральник esbuild.
Процес роботи збиральника не обмежується простим склеюванням файлів в один великий документ. У сучасних інструментах закладені потужні механізми оптимізації. Один з них — це tree shaking, процес інтелектуального видалення невикористовуваного коду, який дозволяє виключити з фінального збирання ті функції та змінні, які були імпортовані з бібліотек, але фактично не викликаються в проєкті. Іншим важливим інструментом є code splitting, або поділ коду. Збиральник автоматично або за вказівкою розробника розбиває монолітний бандл на кілька чанків, які можуть завантажуватися паралельно або ліниво в міру необхідності.
Крім того, bundler виконує мініфікацію коду. Цей процес включає в себе видалення всіх коментарів, зайвих пробілів, скорочення довгих імен змінних і функцій до однобуквених аналогів без зміни логіки роботи додатка. У результаті розмір підсумкового бандла зменшується в рази, що безпосередньо впливає на швидкість скачування ресурсів браузером і загальну продуктивність вебдодатка. Збиральники також часто інтегруються з транспайлерами на кшталт Babel, перетворюючи сучасний код JavaScript на версії, зрозумілі для старих браузерів, і опрацьовують попередні процесори стилів, такі як Sass або Less.
QUESTION: Що таке tree shaking?
Одним з найважливіших завдань при оптимізації сучасних вебдодатків є зменшення об'єму переданого мережею JavaScript-коду. Для досягнення цієї цілі використовується технологія tree shaking, назву якої можна образно перекласти як «потрушування дерева» для того, щоб скинути сухі, непотрібні листки. Суть цього процесу полягає в автоматичному видаленні невикористовуваного коду з підсумкового файлу збирання. Наприклад, якщо ви підключаєте велику бібліотеку утиліт на зразок lodash-es, але використовуєте з неї всього одну функцію, tree shaking гарантує, що у фінальний продукт потрапить тільки ця єдина функція, а весь інший масив невикористовуваного коду буде відкинутий.
Технологія tree shaking нерозривно пов'язана з використанням ES модулів. Оскільки ES модулі мають статичну структуру імпорту та експорту, інструменти збирання можуть точно проаналізувати граф залежностей ще до запуску коду. Збиральник бачить, які саме сутності імпортуються з модуля, а які залишаються незатребуваними, що робить можливим безпечне видалення «зайвого». Без статичного аналізу, притаманного ES модулям (наприклад, у динамічному CommonJS), таке агресивне видалення коду було б технічно неможливим, оскільки функції могли б викликатися динамічно за рядковими іменами.
Для того щоб інструменти збирання коректно застосовували цю оптимізацію до сторонніх бібліотек, розробники пакетів використовують спеціальну властивість sideEffects у файлі package.json. Якщо бібліотека вказує "sideEffects": false, це говорить збирачу про те, що всі модулі пакету чисті і не мають побічних ефектів при простому імпорті (наприклад, не змінюють глобальний об'єкт window і не виконують код автоматично при підключенні). Завдяки цьому збиральник може сміливо видаляти будь-які невикористовувані імпорти з цієї бібліотеки. Підсумком роботи tree shaking стає кардинальне зменшення розміру bundle, що прискорює завантаження сторінки і підвищує загальну продуктивність клієнтської частини додатка.
Что такое source maps?
Современная веб-разработка немыслима без процессов минификации и оптимизации кода, которые делают сайты быстрее, но одновременно усложняют процесс поиска ошибок. Понятие source maps решает эту фундаментальную проблему, обеспечивая прямую связь между минифицированным и исходным кодом. Когда вы пишете код на современном JavaScript, используете модули или фреймворки вроде React и Vue, ваш исходный код перед отправкой на сервер проходит через процесс сборки. В ходе сборки инструменты уменьшают размер файлов, удаляют пробелы, сокращают имена переменных и объединяют десятки скриптов в один большой файл. Если в такой оптимизированной версии возникает ошибка, стандартные инструменты разработчика укажут на строку в скомпилированном файле, где вместо понятного названия переменной будет стоять случайная буква. Именно для отладки в DevTools и создаются source maps.
Технически это специальные файлы с расширением .map, которые содержат математический мост или карту соответствий между сжатым кодом и вашими оригинальными файлами. Браузер автоматически загружает этот файл в фоновом режиме (если он подключен), и разработчик видит в консоли не непонятную минифицированную кашу, а свой родной исходный код со всеми комментариями и правильной нумерацией строк. Для настройки этой возможности в современных сборщиках используется специальная опция devtool в Webpack или аналогичных инструментах, где можно выбрать уровень детализации карты (например, eval-source-map для разработки или hidden-source-map для продакшена). Крайне важно помнить о безопасности и не включать в production публично полные исходные карты, если вы не хотите делать свой исходный код полностью открытым для анализа злоумышленниками. Правильная конфигурация позволяет использовать все преимущества отладки, защищая при этом интеллектуальную собственность компании.
Что такое transpilation?
Мир JavaScript развивается с невероятной скоростью, и стандарты языка обновляются практически ежегодно, добавляя новые синтаксические конструкции, методы и возможности. Однако пользователи часто используют устаревшие браузеры, которые не понимают современный синтаксис, что приводит к критическим ошибкам на сайтах. Процесс transpilation или транспиляция — это преобразование нового JS в старый, обеспечивающее полную совместимость и стабильную работу приложений на любых устройствах. Транспиляторы анализируют современный код (например, синтаксис ES6+ со стрелочными функциями, классами, деструктуризацией) и переписывают его на стандартный ES5, который поддерживается даже самыми архаичными браузерами.
Самым известным и популярным стандартом транспиляции в мире веб-разработки является Babel. Он позволяет разработчикам использовать самые передовые возможности языка сегодня, не дожидаясь, пока все пользователи обновят свои браузеры. Кроме того, TypeScript тоже транспилирует код: популярный язык со строгой типизацией не выполняется напрямую браузером, его компилятор (tsc) сначала преобразует типы и современный синтаксис в обычный чистый JavaScript. Важно понимать, что транспиляция изменяет именно синтаксис кода, но она не добавляет в старые браузеры отсутствующие глобальные объекты или методы, такие как Promise или методы массивов. Для решения этой проблемы транспиляция часто идет рука об руку с использованием полифиллов (polyfills) для новых API, создавая надежный фундамент для кроссбраузерной разработки.
Что такое polyfill?
Даже после успешной транспиляции кода старые браузеры могут столкнуться с проблемой отсутствия поддержки определенных глобальных объектов, методов или функций, которые появились в новых спецификациях языка. На помощь приходит полифилл или polyfill — это специальный кусок кода, добавляющий отсутствующую функциональность в окружение, которое ее изначально не поддерживает. Если старый браузер не знает, что такое промисы или метод массива includes, полифилл проверяет наличие этой функции и, если ее нет, самостоятельно реализует ее стандартным образом. Таким образом, ваш код продолжает работать одинаково хорошо как в самом современном мобильном браузере, так и в устаревшей настольной системе.
Исторически сложилось так, что разработчики использовали различные библиотеки для подключения недостающих функций, но сегодня стандартом индустрии стала библиотека core-js, которая содержит исчерпывающий набор полифиллов для практически любого современного API. Яркими примерами необходимости таких решений являются объекты Promise для работы с асинхронным кодом и функция fetch для выполнения сетевых запросов в устаревших средах. Раньше для автоматизации этого процесса активно использовался пакет @babel/polyfill, однако на сегодняшний день он официально признан устаревшим (deprecated) и больше не рекомендуется к использованию. Вместо него современная экосистема предлагает гибкую настройку core-js в связке с компилятором Babel, что позволяет автоматически подключать только те полифиллы, которые действительно необходимы для целевых браузеров вашего проекта, существенно экономя общий объем загружаемого трафика.
Что такое npm и package.json?
Любой современный проект на JavaScript состоит не только из вашего собственного кода, но и из множества сторонних библиотек, фреймворков и вспомогательных утилит. Для управления этой сложной экосистемой используется npm — официальный менеджер пакетов Node.js, который позволяет скачивать, обновлять и удалять необходимые зависимости одной командой. Центром каждого проекта является файл package.json, представляющий собой конфигурацию проекта в формате JSON. В нем прописана вся метаинформация о приложении, включая его название, версию, автора, а также списки необходимых библиотек для работы приложения и его разработки.
Зависимости в этом файле строго разделяются на категории. В поле dependencies перечисляются библиотеки, которые жизненно необходимы для работы приложения в продакшене (например, React или библиотеки для работы с датами), в то время как в devDependencies указываются инструменты, нужные исключительно на этапе разработки и сборки (например, Webpack, ESLint или TypeScript). Кроме того, важную роль в package.json играет секция scripts, которая содержит пользовательские команды для запуска проекта, сборки, тестирования или линтинга кода, позволяя запускать сложные цепочки команд простым сокращением вроде npm run build. Для обеспечения стабильности и воспроизводимости сборки на любых компьютерах или серверах создается файл package-lock.json, который фиксирует точные версии всех установленных пакетов и их подзависимостей, гарантируя, что у вас и вашего коллеги на устройстве запустится абсолютно идентичный набор кода.
Что такое yarn и pnpm?
В мире разработки программного обеспечения постоянный поиск более быстрых и эффективных инструментов привел к созданию альтернатив стандартному менеджеру пакетов. Пакет yarn — это популярная альтернатива npm от компании Facebook, которая изначально была создана для решения проблем со скоростью установки и безопасностью зависимостей, предложив параллельную загрузку и строгую детерминированность версий через собственный файл yarn.lock. В свою очередь, pnpm представляет собой еще более инновационный подход к управлению зависимостями, который использует жесткие ссылки и символические ссылки из глобального хранилища на уровне всей операционной системы, обеспечивая беспрецедентно эффективное хранение файлов и экономя гигабайты свободного места на жестком диске.
Оба этих инструмента заслужили доверие разработчиков благодаря тому, что они работают значительно быстрее и надежнее традиционного npm, особенно в крупных монорепозиториях или проектах с сотнями зависимостей. Для фиксации версий они используют собственные файлы блокировки: yarn.lock для Yarn и pnpm-lock.yaml для pnpm, которые выполняют ту же функцию, что и package-lock.json, гарантируя одинаковое окружение у всех членов команды. При этом все эти альтернативные менеджеры полной мере совместимы с npm registry, что означает возможность без каких-либо проблем скачивать, использовать и публиковать любые пакеты из официального публичного репозитория JavaScript, просто переключившись на использование новой команды для установки зависимостей в вашем проекте.
QUESTION: Що таке memory leaks?
Витоки пам'яті в JavaScript є ситуацією, коли додаток продовжує утримувати в оперативній пам'яті об'єкти, які більше не потрібні для його роботи. З часом це призводить до серйозної деградації продуктивності, зависань інтерфейсу і навіть повного падіння вкладки браузера.
Основна причина криється в незвільненій пам'яті, коли вбудований збирач сміття фізично не може видалити об'єкт, оскільки на нього все ще існують активні посилання. Частою причиною виникнення подібних проблем стають випадкові або навмисні глобальні змінні, які оголошуються без ключових слів var, let або const, або прив'язуються до об'єкта window в несуворому режимі. Через це такі дані живуть протягом усього життєвого циклу сторінки.
Ще одним поширеним джерелом витоків є забуті таймери та слухачі подій. Якщо ви використовували функції на кшталт setInterval або додали addEventListener до якогось елемента DOM, а потім видалили сам елемент зі сторінки, але забули викликати clearInterval або removeEventListener, то функція зворотного виклику і пов'язані з нею за замкнені змінні залишаться в пам'яті назавжди.
Особливу увагу розробникам варто приділяти замиканням, які захоплюють DOM-елементи. Якщо внутрішня функція посилається на елемент інтерфейсу, цей елемент не буде видалений з пам'яті навіть після його зникнення з дерева документа, оскільки контекст виконання продовжує містити на нього посилання.
Для ефективного пошуку та усунення подібних проблем професійні розробники використовують спеціалізовані вбудовані інструменти. Чудовим прикладом є панель Chrome DevTools Memory, де можна робити знімки купи, порівнювати їх стан до і після певних дій користувача і знаходити залишені в пам'яті від'єднані DOM-вузли або неочищені структури даних.
QUESTION: Що таке garbage collection?
Збирання сміття в JavaScript є механізмом автоматичного керування пам'яттю. Він повністю позбавляє програміста необхідності вручну виділяти і звільняти байти в оперативній пам'яті, як це робиться в низькорівневих мовах на кшталт C або C++.
Головним алгоритмом, який використовується сучасними рушіями для цих цілей, є алгоритм під назвою Mark-and-sweep, що в перекладі означає «помітка і очищення». Цей процес працює циклічно у фоновому режимі.
Збирач сміття починає обхід графа об'єктів від кореневих елементів, таких як об'єкт глобальної області видимості window або global, і позначає всі доступні та досяжні об'єкти як активні та потрібні. Усі ті сутності, до яких неможливо дійти за ланцюжком посилань від коренів, вважаються застарілими та видаляються з пам'яті.
Для тонкого налаштування роботи з пам'яттю та запобігання витокам у складних додатках розробники використовують спеціальні структури даних, такі як WeakMap і WeakSet. Вони зберігають слабкі посилання на об'єкти, що дозволяє гнучко керувати життєвим циклом даних.
Якщо на об'єкт у WeakMap більше немає інших сильних посилань у коді, він автоматично видаляється збирачем сміття, навіть попри те, що він присутній у цій карті. Важливо розуміти, що цей процес є повністю автоматизованим, і програміст не може примусово запустити його вручну з коду скрипта, хоча деякі середовища виконання і надають приховані прапорці для налагодження.
QUESTION: Що таке debounce і throttle?
Техніки debounce і throttle є фундаментальними методами оптимізації продуктивності в JavaScript. Вони дозволяють ефективно контролювати частоту виклику функцій у разі виникнення частих подій, таких як прокрутка сторінки, зміна розміру вікна або введення тексту користувачем.
Метод debounce працює за принципом відкладеного виконання. Він гарантує, що функція буде викликана тільки після того, як припиниться потік подій протягом певної паузи, заданої розробником.
Якщо за час цієї паузи відбувається нова подія, таймер скидається наново. Це ідеально підходить для пошукових рядків з автодоповненням або обробки завершення зміни розміру вікна браузера, коли немає сенсу виконувати важкі обчислення на кожен піксель зміни.
Своєю чергою, метод throttle вирішує протилежне завдання. Він обмежує виконання функції так, щоб вона викликалася не частіше одного разу на заданий інтервал часу, наприклад, рівно один раз на сто мілісекунд.
Це незамінно для таких подій, як прокрутка сторінки scroll або рух миші mousemove, коли реакція інтерфейсу потрібна плавна, але суцільний потік викликів заб'є основний потік процесора. Реалізовувати ці патерни з нуля щоразу не потрібно, оскільки професійні розробники зазвичай використовують готові перевірені рішення з популярних бібліотек, таких як lodash.debounce і lodash.throttle.
QUESTION: Що таке currying?
Каррування в JavaScript є процесом перетворення функції з кількома аргументами, що приймає вигляд f(a, b, c), на ланцюжок функцій, кожна з яких приймає рівно по одному аргументу. У результаті виходить конструкція вигляду f(a)(b)(c).
Цей потужний підхід активно застосовується у функціональному програмуванні для створення більш гнучкого та повторно використовуваного коду. Крім того, він незамінний для реалізації часткового застосування аргументів, коли частина параметрів фіксується заздалегідь, а решта передається пізніше.
Класичним прикладом на сучасному синтаксисі є стрілкова функція const add = a => b => a + b. У цьому випадку виклик add(
Такий підхід дозволяє створювати спеціалізовані функції на основі універсальних шаблонів. Наприклад, функція логування може бути закаррувана так, щоб спочатку приймати рівень важливості повідомлення, а потім сам текст, що спрощує передачу готових логерів у різні модулі додатку.
Крім того, каррування відкриває широкі можливості для композиції функцій, коли вихідні дані однієї операції плавно передаються на вхід іншої. Це робить код декларативним, чистим, легким для тестування та читання в межах великих корпоративних проєктів.
QUESTION: Що таке immutability?
Імутабельність в JavaScript — це концепція програмування, згідно з якою створений об'єкт, масив або примітив не може бути змінений після свого створення. Якщо розробнику потрібно оновити якісь дані в такому об'єкті, він не здійснює мутацію вихідного стану напряму.
Замість цього створюється абсолютно новий об'єкт, у який копіюються старі властивості та перезаписуються тільки ті поля, які потребують змін. На практиці це реалізується за допомогою оператора поширення.
Для цього використовується простий синтаксис вигляду const newObj = { ...obj, key: newVal }, який дозволяє зібрати новий об'єкт за частки секунди. При цьому старий об'єкт зберігається в незмінному вигляді для історії станів або налагодження.
Ручне створення глибоких копій складних структур даних може бути втомливим і схильним до помилок, тому для зручності розробники часто використовують спеціалізовані бібліотеки. Чудовим прикладом є бібліотека Immer, яка дозволяє писати код так, ніби ви змінюєте дані напряму, але під капотом створює імутабельні копії.
Дотримання принципу імутабельності критично важливе для сучасних бібліотек і фреймворків керування станом, таких як React і Redux. Саме незмінність даних дозволяє компонентам миттєво і без зайвих витрат визначати факт зміни стану, порівнюючи посилання на об'єкти, що безпосередньо впливає на швидкість рендерингу та стабільність користувацького інтерфейсу.
QUESTION: Що таке Web Components?
Технологія Web Components є потужним набором стандартизованих вебплатформних API, які дозволяють створювати нові, користувацькі, інкапсульовані та багаторазові теги для використання у вебдодатках та на вебсторінках без необхідності підключення сторонніх важких бібліотек чи фреймворків на кшталт React, Vue або Angular. Ця технологія кардинально змінює підхід до розробки користувацьких інтерфейсів, роблячи компоненти по-справжньому незалежними та переносимими між проєктами.
Фундаментальна основа Web Components будується на кількох ключових специфікаціях. Першою з них є Custom Elements, які дають розробникам можливість визначати свої власні теги та повністю контролювати їх життєвий цикл, включаючи створення, додавання в DOM, видалення та оновлення атрибутів. Другий найважливіший компонент — це Shadow DOM, що забезпечує ізоляцію стилів та розмітки. Завдяки йому внутрішні стилі компонента ніколи не протечуть назовні й не зіпсують глобальний дизайн сайту, а зовнішні стилі не зможуть випадково зламати верстку всередині елемента. Третім елементом виступають HTML Templates, представлені тегами template і slot, які дозволяють визначати незмінні шаблони розмітки, що клонуються в міру необхідності.
На практиці створення такого елемента виглядає наступним чином: розробник створює клас, що успадковується від HTMLElement, у методі constructor якого налаштовує Shadow DOM і додає в нього розмітку. Потім використовується метод customElements.define(), що зв'язує ім'я нового тега, наприклад my-element, зі створеним класом. Після цього даний тег можна вільно використовувати в звичайному HTML-документі так само, як стандартні елементи div або span, що забезпечує максимальну гнучкість і універсальність сучасної веб-розробки.
[ITEM 2] QUESTION: Що таке Canvas API?
Canvas API є одним із найпотужніших інструментів сучасної веб-розробки, вбудованим в HTML5, який надає розробникам можливість програмного малювання 2D-графіки безпосередньо в браузері за допомогою мови JavaScript. Цей програмний інтерфейс відкриває величезні можливості для створення інтерактивних графічних редакторів, графіків, візуалізацій даних, захопливих браузерних ігор та складних динамічних інтерфейсів без використання зовнішніх графічних редакторів або плагінів.
Для початку роботи з графікою на сторінці створюється HTML-елемент canvas, після чого у скрипті отримується спеціальний контекст отрисовки за допомогою виклику методу canvas.getContext('2d'). Саме цей об'єкт контексту стає головним інструментом для всіх подальших маніпуляцій. Розробник отримує у своє розпорядження багатий набір методів для створення візуальних образів. Наприклад, функції fillRect і strokeRect дозволяють миттєво малювати заповнені або контурні прямокутники із заданими координатами та розмірами, а метод arc використовується для створення кіл, дуг і складних заокруглених елементів управління.
Крім геометричних примітивів, Canvas API чудово справляється з растровими зображеннями. Метод drawImage дає можливість завантажувати на полотно картинки, фотографії та спрайти, масштабувати їх і вирізати окремі фрагменти для покадрової анімації. Для створення плавних, безперервних анімацій із високою частотою кадрів використовується функція requestAnimationFrame, яка синхронізує процес отрисовки з частотою оновлення екрана монітора або мобільного пристрою. Це гарантує максимальну продуктивність, відсутність лагів і плавність рухомих об'єктів, роблячи інтерфейс чуйним та професійним.
[ITEM 3] QUESTION: Що таке WebGL?
Технологія WebGL є низькорівневим програмним інтерфейсом для мови JavaScript, який дозволяє виконувати рендеринг високопродуктивної інтерактивної тривимірної та двовимірної графіки прямо у вікні будь-якого сучасного веббраузера. Головною перевагою WebGL є його здатність задіяти апаратне прискорення графічного процесора комп'ютера або мобiльного пристрою, що забезпечує колосальну продуктивність і дозволяє обробляти складні полігональні сітки, мільйони вершин та важкі візуальні ефекти в реальному часі.
Щоб почати роботу з цією технологією, розробник створює на сторінці стандартний елемент canvas, а потім запитує у нього графічний контекст через виклик методу canvas.getContext('webgl') або його більш сучасну версію webgl2. На відміну від звичного 2D-контексту, WebGL вимагає принципово іншого підходу до програмування. Основою його роботи є шейдери — спеціальні мініпрограми, які компілюються прямо на відеокарті й пишуться спеціалізованою мовою програмування GLSL. Вершинні шейдери відповідають за трансформацію координат тривимірних точок у двоекранні проєкції, а піксельні або фрагментні шейдери визначають точний колір кожного окремого пікселя з урахуванням освітлення, тіней і текстур.
Оскільки написання чистого коду на WebGL вимагає глибоких знань математики, лінійної алгебри та роботи з буферами пам'яті, на практиці розробники часто використовують популярні бібліотеки-обгортки, такі як Three.js. Ця бібліотека значно спрощує роботу: вона бере на себе рутину зі створення сцен, камер, джерел світла та завантаження 3D-моделей, перетворюючи складний низькорівневий процес на лаконічний і зрозумілий JavaScript-код. Завдяки цьому WebGL активно застосовується для створення інтерактивних 3D-турів, презентацій продуктів, віртуальних музеїв та передових веб-ігор.
[ITEM 4] QUESTION: Що таке Web Audio API?
Web Audio API є потужною, гнучкою та високопродуктивною системою для складної обробки, синтезу та відтворення звуку прямо у веббраузері за допомогою JavaScript. Цей інтерфейс створений для того, щоб вийти за рамки стандартного HTML-тега audio, надаючи розробникам повний контроль над аудіосигналами, можливість створення просторових звукових ефектів, динамічного мікшування та генерації музики «на льоту» в реальному часі.
В основі архітектури Web Audio API лежить концепція аудіографа, що складається з взаємопов'язаних вузлів. Усі маніпуляції зі звуком відбуваються всередині спеціального об'єкта AudioContext, який керує станом та таймінгом усіх звукових операцій. Джерела звуку, такі як аудіофайли, мікрофон або вбудовані генератори хвиль (Oscillator), підключаються до ланцюжка вузлів, що обробляють. Наприклад, вузол Gain відповідає за регулювання гучності та затухання сигналу, а різні фільтри (Filter nodes) дозволяють вирізати певні частотні діапазони, створюючи ефекти приглушеного звуку або відлуння.
Однією з найважливіших особливостей API є наявність інструментів для глибокого аналізу аудіопотоку в реальному часі, таких як AnalyserNode. З його допомогою можна зчитувати частотні характеристики та амплітуду звуку, що дозволяє створювати красиві візуалізатори музики, які реагують на баси та ритм. Крім того, Web Audio API підтримує просторовий звук (PannerNode), який імітує тривимірне звукове середовище. Змінюючи координати слухача та джерела звуку, можна домогтися реалістичного ефекту переміщення звуку навколо користувача, що є критично важливим для сучасних браузерних тривимірних ігор та VR-додатків.
[ITEM 5] QUESTION: Які нові можливості ES2023+?
Стандарт ECMAScript з 2023 року та наступні версії принесли в мову JavaScript безліч довгоочікуваних і корисних можливостей, які значно покращили повсякденну роботу розробників, підвищили чистоту коду та продуктивність додатків. Ці нововведення торкнулися роботи з масивами, асинхронними операціями, безпекою та синтаксисом, роблячи мову ще більш виразною і потужним інструментом для створення масштабних програмних продуктів.
Значним кроком вперед стало поліпшення роботи з масивами, зокрема додавання незмінних методів Array.prototype.toSorted, toReversed і методу with(). Раніше стандартні методи sort() і reverse() мутували вихідний масив, що часто призводило до важковловимих багів у додатках з архітектурою на кшталт Redux. Нові методи повертають новий масив з уже застосованими змінами, залишаючи оригінал у недоторканності. Метод with(index, value) дозволяє безпечно замінити елемент за конкретним індексом, також створюючи копію масиву. Ще одним прекрасним доповненням став метод Array.fromAsync(), що суттєво спрощує створення масивів з асинхронних ітераторів або промісів.
Крім масивів, мова поповнилася зручними синтаксичними фішками. З'явилася підтримка Hashbang grammar на початку файлів скриптів, що дозволяє запускати файли JS безпосередньо в терміналі як виконувані скрипти в Unix-подібних системах. Також розширилися можливості використання об'єктів Symbol як ключів у колекціях WeakMap, що відкриває нові патерни для створення приватних властивостей та ефективного управління пам'яттю без ризику витоків. Усі ці зміни демонструють прагнення творців мови зробити JavaScript максимально зручним, безпечним і готовим до вирішення найскладніших завдань сучасної розробки.