Основи обробки подій у JavaScript: від спливання до делегування
На початкових етапах мого знайомства з веб-розробкою, одним із цікавих та несподіваних аспектів виявилось спливання подій. Спочатку це могло здатися незвичним, але розібравшись, стає зрозуміло, чому це важливо. Кожен веб-розробник неминуче зіткнеться з поняттям подій. Тож, що ж таке подія?
JavaScript використовує події для забезпечення взаємодії користувача з веб-сторінками. Подія – це дія або випадок, який може бути виявлений та на який можна відреагувати за допомогою коду. Прикладами є кліки мишею, натискання клавіш на клавіатурі або відправка форми.
Для виявлення та обробки цих подій JavaScript використовує слухачі подій. Слухач подій – це функція, яка чекає на настання певної події на сторінці, наприклад, клік по кнопці. Коли слухач події фіксує очікувану подію, він реагує виконанням коду, пов’язаного з нею. Цей процес виявлення та реагування на події називається обробкою подій.
Розглянемо ситуацію, коли на сторінці є три елементи: div, span та кнопка. Кнопка вкладена в span, а span, своєю чергою, вкладений в div. Ось приклад структури:
Припустимо, що кожен з цих елементів має слухач подій, який очікує на клік та виводить повідомлення в консоль. Що трапиться, коли ми натиснемо на кнопку?
Щоб перевірити це самостійно, створіть папку, а в ній три файли: index.html, style.css та app.js.
В index.html додайте наступний код:
<html lang="uk"> <head> <title>Спливання подій</title> <link rel="stylesheet" href="https://wilku.top/the-hidden-key-to-dynamic-web-interactions/style.css"> </head> <body> <div> <span><button>Натисни мене!</button></span> </div> <script src="app.js"></script> </body> </html>
У style.css додайте код для стилізації елементів div та span:
div { border: 2px solid black; background-color: orange; padding: 30px; width: 400px; } span { display: inline-block; background-color: cyan; height: 100px; width: 200px; margin: 10px; padding: 20px; border: 2px solid black; }
У файл app.js додайте код, який додає слухачі подій до елементів div, span і button. Кожен слухач очікує на подію кліку:
const div = document.querySelector('div'); div.addEventListener('click', () => { console.log("Ви клікнули на елемент div"); }); const span = document.querySelector('span'); span.addEventListener('click', () => { console.log("Ви клікнули на елемент span"); }); const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Ви клікнули на кнопку"); });
Відкрийте index.html у браузері, перевірте сторінку і клікніть на кнопку. Що ви помітили? Ось результат натискання кнопки:
Клік по кнопці запускає обробник подій, що відповідає за клік по кнопці. Проте, обробники подій для елементів span та div також виконуються. Чому так відбувається?
Клік по кнопці запускає обробник подій кнопки, який виводить повідомлення в консоль. Оскільки кнопка вкладена в span, то натискання на кнопку технічно також є кліком по span, тому запускається і його обробник подій.
Аналогічно, оскільки span вкладений в div, клік по span також є кліком по div, і це викликає виконання його обробника подій. Це і є спливання подій.
Спливання подій
Спливання подій — це процес, коли подія, яка виникла у вкладеному наборі HTML елементів, поширюється, або «спливає», від самого внутрішнього елемента, де вона виникла, вгору по DOM-дереву до кореневого елемента, запускаючи всі обробники подій, які прослуховують цю подію.
Слухачі подій виконуються в порядку, який відповідає тому, як подія поширюється вгору по DOM-дереву. Розглянемо наступне DOM-дерево, яке відображає HTML структуру, використану у цьому прикладі:
Дерево DOM показує кнопку, вкладену в span, який вкладений в div, а той вкладений в body, а body, в свою чергу, вкладений в елемент HTML. Коли ми клікаємо на кнопку, подія кліку запускає слухач подій, прикріплений до кнопки.
Оскільки елементи вкладені, подія «підіймається» вгору по DOM-дереву до елементу span, потім div, потім body і, нарешті, до елементу HTML, запускаючи всі слухачі подій, які очікують подію кліку в цьому порядку.
Ось чому виконуються слухачі подій, прикріплені до span та div. Якби у нас були слухачі подій для кліків на body та HTML, вони б теж були запущені.
Вузол DOM, де виникає подія, називається ціллю. В нашому випадку, оскільки клік відбувається на кнопці, ціллю події є елемент кнопки.
Як зупинити спливання подій
Щоб зупинити поширення події в DOM, використовується метод stopPropagation(), який є доступним для об’єкта події. Розглянемо код, що додає слухач подій до елемента кнопки:
const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Ви клікнули на кнопку"); });
Цей код призводить до спливання події в DOM-дереві, коли користувач натискає кнопку. Щоб зупинити це спливання, ми викликаємо метод stopPropagation(), як показано нижче:
const button = document.querySelector('button'); button.addEventListener('click', (e) => { console.log("Ви клікнули на кнопку"); e.stopPropagation(); });
Обробник події – це функція, яка виконується при натисканні на кнопку. Слухач подій автоматично передає об’єкт події в обробник події. У нашому випадку, цей об’єкт представлений змінною e, яка є параметром обробника події.
Об’єкт події e містить інформацію про подію, а також надає доступ до різних властивостей та методів, пов’язаних з подіями. Одним з таких методів є stopPropagation(), який використовується для зупинки спливання подій. Виклик stopPropagation() в обробнику події кнопки запобігає поширенню події в DOM-дереві, починаючи з елемента кнопки.
Ось результат натискання на кнопку після додавання методу stopPropagation():
Ми використовуємо stopPropagation(), щоб запобігти спливанню події з елемента, для якого ми його використовуємо. Наприклад, якщо нам потрібно, щоб подія кліку поширювалася від елемента кнопки до елемента span, а не далі вгору по DOM-дереві, ми б використали stopPropagation() для слухача подій span.
Перехоплення подій
Перехоплення подій є протилежністю спливанню подій. При перехопленні події, подія просувається від найзовнішнього елемента до цільового елемента, як показано нижче:
Наприклад, у нашому випадку, коли ми натискаємо на кнопку, при перехопленні подій, першими запускаються слухачі подій на елементі div. Далі йдуть слухачі на span, і, нарешті, запускаються слухачі на цільовому елементі.
Спливання подій є стандартним способом поширення подій у DOM. Щоб змінити поведінку на перехоплення подій, ми передаємо третій аргумент до слухачів подій, встановлюючи значення true для перехоплення. Якщо цей аргумент не передається, перехоплення подій за замовчуванням буде false.
Розглянемо наступний слухач подій:
div.addEventListener('click', () => { console.log("Ви клікнули на елемент div"); });
Оскільки тут немає третього аргументу, перехоплення встановлено як false. Щоб встановити true, ми передаємо третій аргумент: true.
div.addEventListener('click', () => { console.log("Ви клікнули на елемент div"); }, true);
Або можна передати об’єкт:
div.addEventListener('click', () => { console.log("Ви клікнули на елемент div"); }, {capture: true});
Щоб перевірити перехоплення подій, додайте третій аргумент до всіх слухачів у файлі JavaScript:
const div = document.querySelector('div'); div.addEventListener('click', () => { console.log("Ви клікнули на елемент div"); }, true); const span = document.querySelector('span'); span.addEventListener('click', () => { console.log("Ви клікнули на елемент span"); }, true); const button = document.querySelector('button'); button.addEventListener('click', () => { console.log("Ви клікнули на кнопку"); }, true);
Тепер відкрийте браузер і натисніть на кнопку. Результат буде таким:
Зверніть увагу, що на відміну від спливання подій, де вивід з кнопки був першим, при перехопленні подій, першим йде вивід з зовнішнього елемента, div.
Спливання та перехоплення подій є основними способами поширення подій в DOM. Однак, спливання подій використовується для розповсюдження подій найчастіше.
Делегування подій
Делегування подій – це підхід, коли один слухач подій прикріплений до спільного батьківського елемента, наприклад, до елемента <ul>, замість того, щоб встановлювати окремі слухачі для кожного дочірнього елемента. Події на дочірніх елементах потім «спливають» до батьківського елемента, де їх обробляє обробник подій, встановлений на батьківському елементі.
У випадку, коли є батьківський елемент з дочірніми, ми додаємо слухача подій тільки до батьківського елемента. Цей обробник буде обробляти події всіх дочірніх елементів.
Ви можете запитати, як батьківський елемент дізнається, який саме дочірній елемент був натиснутий. Як зазначалося, слухач подій передає об’єкт події до обробника. Цей об’єкт має методи та властивості, що надають інформацію про подію. Однією з таких властивостей є target. Вона вказує на конкретний HTML-елемент, де виникла подія.
Наприклад, якщо у нас є невпорядкований список з елементами, і ми прикріпимо слухач події до елемента <ul>, то коли подія відбувається в елементі списку, властивість target об’єкта події буде вказувати на конкретний елемент списку, де відбулась подія.
Щоб побачити делегування подій в дії, додайте наступний HTML-код до існуючого файлу:
<ul> <li>Toyota</li> <li>Subaru</li> <li>Honda</li> <li>Hyundai</li> <li>Chevrolet</li> <li>Kia</li> </ul>
Додайте код JavaScript, що використовує делегування подій, для використання одного слухача подій на батьківському елементі для всіх дочірніх:
const ul = document.querySelector('ul'); ul.addEventListener('click', (e) => { // цільовий елемент targetElement = e.target // виводимо в консоль текст цільового елемента console.log(targetElement.textContent); });
Тепер відкрийте браузер і клацніть на будь-який елемент списку. Вміст елемента буде виведений в консоль:
Використовуючи один слухач подій, ми обробляємо події всіх дочірніх елементів. Велика кількість слухачів подій на сторінці впливає на її продуктивність, споживає більше пам’яті та сповільнює завантаження сторінки.
Делегування подій дозволяє уникнути цих проблем, мінімізуючи кількість слухачів подій. Делегування залежить від спливання подій. Отже, можна сказати, що спливання подій допомагає оптимізувати продуктивність веб-сторінок.
Поради для ефективної обробки подій
Як розробник, працюючи з подіями в DOM, використовуйте делегування подій замість встановлення великої кількості слухачів подій для елементів.
При делегуванні подій прикріплюйте слухач до найближчого спільного предка дочірніх елементів, що потребують обробки подій. Це допомагає оптимізувати спливання подій та мінімізує шлях події до її обробки.
Використовуйте об’єкт події, наданий слухачем, для отримання необхідної інформації. Наприклад, властивість target стане в нагоді при обробці подій.
Щоб мати більш продуктивні веб-сайти, уникайте зайвих маніпуляцій з DOM. Події, які викликають часті маніпуляції з DOM, можуть негативно вплинути на продуктивність веб-сайту.
При роботі з вкладеними елементами будьте обережні, прикріплюючи слухачі подій до вкладених елементів. Це може вплинути на продуктивність, а також ускладнити обробку подій та підтримку коду.
Висновок
Події є потужним інструментом в JavaScript. Спливання подій, перехоплення подій та делегування подій є важливими концепціями обробки подій. Ця стаття допоможе веб-розробникам краще зрозуміти ці поняття та створювати більш інтерактивні, динамічні та продуктивні веб-сайти та додатки.