До цього моменту ми розглядали JavaScript як ізольовану мову програмування: вивчали змінні, функції та масиви. Проте в браузері головне завдання JS — це взаємодія з веб-сторінкою. Нам потрібно мати можливість прочитати те, що користувач ввів у форму, змінити колір кнопки при кліку, показати вспливаюче вікно або плавно додати новий товар у кошик.
Для цього браузер надає JavaScript’у спеціальний інструмент — DOM (Document Object Model). Це своєрідний “міст” між HTML-кодом та JS. Окрім того, користувачі взаємодіють зі сторінкою непередбачувано: клікають мишкою, скролять, натискають клавіші. Щоб обробляти ці дії послідовно і не “підвісити” браузер, JavaScript використовує унікальний механізм керування чергою завдань, який називається Event Loop.
Мета цієї лекції — навчитися “оживляти” HTML-документ за допомогою JS, реагувати на дії користувача та розібратися, як браузер виконує код під капотом.
Браузер отримує від сервера звичайний текстовий HTML-файл. Перш ніж намалювати сторінку, він читає цей текст і будує в оперативній пам’яті його внутрішнє представлення у вигляді дерева. Це дерево і називається DOM.
У цьому дереві кожен HTML-тег стає об’єктом (який називають вузлом — Node).
Наприклад, тег <body> стає об’єктом з властивостями, <p> всередині нього стає дочірнім об’єктом і так далі.
Головним “входом” у це дерево є глобальний об’єкт document.
Майже всі маніпуляції зі сторінкою починаються зі звернення до об’єкта document.
Щоб змінити якийсь елемент, спочатку його треба знайти у DOM-дереві і зберегти у змінну. У сучасному JS для цього використовують два основних методи, які працюють точно так само, як і CSS-селектори.
document.querySelector(selector)Шукає і повертає перший знайдений елемент, який відповідає CSS-селектору. Якщо нічого не знайдено — повертає null.
// Знайти елемент за класом
const btn = document.querySelector(".submit-btn");
// Знайти елемент за ID (хоча для ID є спеціальний метод getElementById)
const header = document.querySelector("#main-header");
// Складний CSS-селектор
const firstListItem = document.querySelector(".menu > li:first-child");
document.querySelectorAll(selector)Шукає і повертає всі елементи, що відповідають селектору. Зверніть увагу: він повертає структуру NodeList (псевдомасив). Її не можна просто так змінити (наприклад items.style.color = 'red' — помилка). Потрібно перебирати її циклом forEach.
// Знайти всі параграфи в статті
const paragraphs = document.querySelectorAll("article p");
paragraphs.forEach((p) => {
p.style.color = "gray"; // Змінюємо колір кожного знайденого параграфа
});
Після того, як ми знайшли елемент і поклали його в змінну, ми отримуємо доступ до його властивостей, які можна читати або перезаписувати.
Зміна тексту та HTML:
element.textContent — читає або записує лише безпечний текст (без HTML-тегів).element.innerHTML — дозволяє вставити в елемент повноцінний HTML-код (наприклад <br> або <b>), який браузер відрендерить. Небезпечно використовувати для вставки даних, отриманих від користувача (може призвести до XSS атак).const title = document.querySelector("h1");
title.textContent = "Новий заголовок!";
Зміна стилів:
Будь-яка CSS властивість доступна через об’єкт style. Важливо: якщо в CSS властивість пишеться через дефіс (background-color), то в JS використовується camelCase (backgroundColor).
btn.style.backgroundColor = "red";
Керування класами (classList):
Це найправильніший спосіб стилізації. Замість того, щоб міняти inline-стилі (.style), краще додавати або забирати CSS-класи, заздалегідь написані у файлі стилів.
element.classList.add('active') — додає клас.element.classList.remove('hidden') — видаляє клас.element.classList.toggle('dark-mode') — перемикач: додає клас, якщо його немає, і видаляє, якщо є.DOM дозволяє динамічно створювати нові HTML-теги, яких не було в початковому документі.
// 1. Створюємо новий елемент в ПАМ'ЯТІ (він ще не на сторінці)
const newDiv = document.createElement("div");
newDiv.textContent = "Я щойно народився!";
newDiv.classList.add("alert");
// 2. Знаходимо "батька" на сторінці
const container = document.querySelector(".container");
// 3. Вставляємо новий елемент ('дітям' в кінець)
container.appendChild(newDiv);
// Альтернатива: container.prepend(newDiv) - вставить на початок
Видалення:
const oldAd = document.querySelector(".advertisement");
oldAd.remove(); // Елемент назавжди зникає зі сторінки
Сайт має реагувати на дії користувача: кліки, рух мишки, введення тексту, відправку форми. Це називається подіями (Events). Для того, щоб “слухати” події, до DOM-елемента потрібно “причепити ухо” — Event Listener.
Синтаксис: element.addEventListener(ім'я_події, функція_обробник)
const myBtn = document.querySelector("#clickMe");
// Коли станеться подія 'click', JS викличе передану стрілочну функцію
myBtn.addEventListener("click", () => {
alert("Кнопку натиснуто!");
});
Популярні події: click, mouseenter, mouseleave, keydown, submit (для форм), input (для полів вводу).
Об’єкт події (Event Object):
Щоразу, коли стається подія, браузер автоматично створює об’єкт з усіма деталями цієї події і передає його першим аргументом у нашу функцію (зазвичай його називають e або event).
document.addEventListener("keydown", (event) => {
console.log(`Ви натиснули клавішу: ${event.key}`);
// event.key містить літеру (наприклад "Enter" або "A")
});
Уявіть, що у вас є кнопка всередині <div>, який лежить у <body>.
Якщо ви клікаєте на кнопку, подія click насправді відбувається не лише на кнопці. Подібно до бульбашки повітря під водою, подія кліку утворюється на найменшому елементі (кнопці) і починає “спливати” вгору до предків: на <div>, потім на <body>, аж до document.
Це називається Спливання (Event Bubbling).
Завдяки спливанню ми можемо застосувати потужний патерн — Делегування подій.
Уявіть, що у вас є список з 1000 товарів (тегів <li>), і кожен треба обробляти по кліку. Вішати 1000 Event Listeners на кожен <li> — це катастрофа для пам’яті комп’ютера.
Натомість, ми вішаємо ОДИН обробник на їхнього спільного батька (наприклад, <ul>), і коли користувач клікає на будь-який <li>, подія спливає до <ul>, де ми її і перехоплюємо!
const list = document.querySelector("ul.products");
list.addEventListener("click", (event) => {
// event.target — це той найменший елемент, на який ДІЙСНО клікнули
if (event.target.tagName === "LI") {
event.target.style.backgroundColor = "yellow";
}
});
Це найулюбленіше питання на співбесідах рівня Middle.
JavaScript — однопотоковий (Single-threaded). Це означає, що він має лише одного “робітника”, який може виконувати лише одне завдання одночасно.
Але що робити, якщо нам потрібно зробити паузу (наприклад, таймер setTimeout на 5 секунд), або завантажити величезну фотографію з сервера? Якби робітник просто чекав 5 секунд, то вся сторінка зависла б (кнопки перестали б натискатися, скрол би зламався).
Для вирішення цієї проблеми браузер надає допоміжну екосистему — Web API та Event Loop.
Як це працює крок за кроком:
console.log), він виконує її негайно.setTimeout або запит до бази даних), він НЕ чекає! Він аутсорсить (віддає) це завдання самому браузеру (у Web API) і каже браузеру: “Коли закінчиш рахувати 5 секунд, поклади цю функцію у чергу”.console.log("1. Я виконуюсь першим");
setTimeout(() => {
console.log("2. Я виконаюсь третім! Хоча таймер 0 секунд");
}, 0);
console.log("3. Я виконуюсь другим");
Чому setTimeout виконався останнім, хоча час очікування 0 секунд?
Тому що setTimeout — це асинхронне Web API. Його callback-функція була відкинута в Web API, одразу ж перемістилася в Чергу Завдань (Task Queue). Але Event Loop не має права пустити функцію з черги в Стек, доки Стек не спорожніє. Тому спочатку виведуться 1 і 3 (синхронний код у стеку), Стек звільниться, і лише тоді Event Loop закине функцію з таймера (2) на виконання.
Наступна лекція проллє світло на інший вид черги — Мікротаски (Microtasks), куди потрапляють Проміси (Promises). Мікротаски завжди мають вищий пріоритет над звичайними Макротасками (наприклад
setTimeout).
querySelector (повертає один елемент) та querySelectorAll (повертає колекцію).classList (а не напряму через стилі style).addEventListener дозволяє підписатися на події. Завжди використовуйте його замість застарілих атрибутів onclick у HTML..btn на сторінці? Який тип даних він поверне?innerHTML для вставки даних, які ввів користувач? Яка властивість безпечніша?classList, а не пряму зміну властивості style?<li> з текстом “Яблуко” і додасть його в кінець списку <ul> з класом .fruits.event) такий важливий у патерні “Делегування”?for на мільярд ітерацій (синхронний код). Що станеться зі сторінкою? Чому?