Що таке атрибути даних у HTML
Data attributes (атрибути даних) - це HTML-атрибути з префіксом data-, що зберігають метадані застосунку безпосередньо на елементах. JavaScript читає їх через element.dataset, а HTML залишається валідним.
Теорія
TL;DR
- Уяви стікер на фізичному предметі: ти додаєш додаткову інформацію до елемента, не змінюючи сам елемент
- Префікс
data-сигналізує браузеру, що це навмисні дані застосунку, а не порушення специфікації element.dataset.productIdчитаєdata-product-idавтоматично (kebab-case перетворюється на camelCase)- Data attributes завжди є рядками: числа і JSON потрібно перетворювати вручну перед використанням
- Правило вибору:
data-*для метаданих, які читає JavaScript; ARIA - для доступності; класи - для стилів
Швидкий приклад
<button data-product-id="42" data-category="electronics">
Купити
</button>
<script>
const button = document.querySelector("button");
console.log(button.dataset.productId); // "42"
console.log(button.dataset.category); // "electronics"
// Також працює через getAttribute
console.log(button.getAttribute("data-product-id")); // "42"
</script>dataset - це живий об'єкт DOMStringMap. Зміни у dataset одразу оновлюють HTML-атрибут, і навпаки.
Ключова відмінність від нестандартних атрибутів
До появи data-* розробники писали <div user-id="123"> або тягнули ID прямо в назви класів. Обидва підходи або давали невалідний HTML, або перетворювали код на головоломку. Префікс data- - це офіційний спосіб W3C додавати кастомні метадані, і браузер парсить їх у чистий об'єкт dataset, який зручно видно в DevTools.
Коли використовувати
- Зберігання ID для обробників подій:
<button data-user-id="123">, потім читаємо в click handler - Делегування подій (event delegation): ідентифікатори на дочірніх елементах, зчитуємо через
e.target.closest('[data-todo-id]') - Ініціалізація JavaScript-компонентів:
<div data-chart-type="line" data-refresh-interval="5000"> - Серверні шаблони: Rails, Django, PHP вставляють динамічні значення з бази даних у
data-*атрибути - Селектори для тестування:
data-testid- стандарт у Cypress і Playwright
Уникай data attributes для: чутливих даних (видно в HTML-коді і DevTools), великих об'єктів (краще <script> з JSON), значень що оновлюються в щільних циклах (кожен запис у DOM - окрема операція).
Як браузер це обробляє
При парсингу HTML браузер створює DOMStringMap на DOM-вузлі кожного елемента. Це живий проксі до всіх data-* атрибутів. При зверненні до element.dataset.productId браузер конвертує camelCase назад у product-id, шукає data-product-id і повертає рядкове значення. Запис також двонаправлений: element.dataset.newProp = 'value' створює data-new-prop у DOM.
Типові помилки
Забути, що data attributes завжди є рядками
// НЕПРАВИЛЬНО: покладається на неявне приведення типів у JS
const button = document.querySelector('[data-count]');
button.dataset.count = '5';
if (button.dataset.count > 3) { // працює випадково
console.log('Багато');
}
// ПРАВИЛЬНО: явне перетворення типу
const count = parseInt(button.dataset.count, 10);
if (count > 3) {
console.log('Багато');
}Зберігати JSON без серіалізації
// НЕПРАВИЛЬНО: збережеться "[object Object]"
element.dataset.config = { theme: 'dark', lang: 'en' };
// ПРАВИЛЬНО: stringify перед записом, parse при читанні
element.dataset.config = JSON.stringify({ theme: 'dark', lang: 'en' });
const config = JSON.parse(element.dataset.config);
console.log(config.theme); // "dark"Оновлювати data attributes у щільному циклі
// НЕПРАВИЛЬНО: 1000 записів у DOM
for (let i = 0; i < 1000; i++) {
element.dataset.score = i;
}
// ПРАВИЛЬНО: обчислення в JS, один запис у DOM наприкінці
let score = 0;
for (let i = 0; i < 1000; i++) {
score = i;
}
element.dataset.score = score;Зберігати чутливі дані
<!-- НЕПРАВИЛЬНО: видно всім хто відкриє DevTools -->
<div data-api-key="sk-1234567890abcdef"></div>
<!-- ПРАВИЛЬНО: отримуй секрети через захищений endpoint -->
<script>
const apiKey = await fetch('/api/get-key').then(r => r.json());
</script>Де зустрічається на практиці
- React:
data-testidдля селекторів у Cypress і Playwright - jQuery-плагіни:
$('[data-toggle="modal"]')для ініціалізації компонентів - Аналітика: бібліотеки читають
data-event-*атрибути для трекінгу кліків - Web Components: передача початкової конфігурації до завантаження JavaScript
- CSS:
[data-priority="high"] { color: red; }для стилів на основі атрибутів
Питання на співбесіді
Q: Чим data attributes відрізняються від зберігання даних у JS-властивостях елемента?
A: Data attributes зберігаються в HTML і не зникають при cloneNode() та серверній серіалізації. JS-властивості - зникають. Також data attributes видно в DevTools і можна задати з серверних шаблонів. Для метаданих, що є частиною визначення елемента, обирай data-*; для стану виконання - JS-властивості.
Q: Чи можна використовувати data attributes у CSS?
A: Так. Селектори атрибутів працюють: [data-priority="high"] { color: red; }. Значення можна читати в псевдоелементах через content: attr(data-label). Але CSS може тільки перевіряти наявність або точне значення, не обчислювати. Для динамічної стилізації на основі даних що змінюються, краще додавати і видаляти класи через JavaScript.
Q: Що відбувається з великими літерами в назвах data атрибутів?
A: HTML-атрибути регістронезалежні, тому data-ProductID стає data-productid у DOM. Використовуй тільки малі літери і дефіси. API dataset конвертує дефіси в camelCase: data-product-id стає dataset.productId.
Q: (Senior) Наскільки повільний доступ через dataset порівняно з кешованою JS-змінною?
A: Кожне звернення до dataset виконує пошук атрибута, що трохи повільніше за читання локальної змінної. Для значень до яких звертаєшся часто, кешуй один раз: const id = element.dataset.id;, далі використовуй id. Для великих об'єктів зберігай у атрибуті ключ посилання, а сам об'єкт тримай у JavaScript Map.
Приклади
Делегування подій з data attributes
Один слухач на батьківському елементі замість окремих слухачів на кожному дочірньому. Data attributes дають кожному дочірньому елементу унікальну ідентичність без додаткових JavaScript-об'єктів:
<ul id="todo-list">
<li data-todo-id="1" data-priority="high">
<span>Виправити баг в авторизації</span>
<button class="delete-btn">Видалити</button>
</li>
<li data-todo-id="2" data-priority="low">
<span>Оновити документацію</span>
<button class="delete-btn">Видалити</button>
</li>
</ul>
<script>
document.getElementById('todo-list').addEventListener('click', (e) => {
// Йдемо вгору по DOM до найближчого li з data-todo-id
const todoItem = e.target.closest('[data-todo-id]');
if (!todoItem) return;
const todoId = todoItem.dataset.todoId;
const priority = todoItem.dataset.priority;
if (e.target.classList.contains('delete-btn')) {
console.log(`Видаляємо todo ${todoId} (пріоритет: ${priority})`);
// Виведе: "Видаляємо todo 1 (пріоритет: high)"
todoItem.remove();
}
});
</script>e.target.closest('[data-todo-id]') йде вгору по DOM від елемента на якому відбувся клік до першого відповідного предка. Це коректно обробляє кліки по вкладених <span> або <button> всередині <li>.
Передача серверних даних у JavaScript
Серверні шаблони можуть вбудовувати динамічні значення в data attributes. JavaScript зчитує їх при завантаженні сторінки без додаткового API-запиту:
<!-- Сервер рендерить це з реальними значеннями з бази даних -->
<div
id="user-profile"
data-user-id="99"
data-role="admin"
data-locale="uk-UA"
>
Вітаємо, Олексій
</div>
<script>
const profile = document.getElementById('user-profile');
const userId = profile.dataset.userId; // "99"
const role = profile.dataset.role; // "admin"
const locale = profile.dataset.locale; // "uk-UA"
if (role === 'admin') {
console.log(`Адмін ${userId}, локаль: ${locale}`);
// Виведе: "Адмін 99, локаль: uk-UA"
}
</script>Для невеликих конфігурацій окремий JSON-endpoint не потрібен. Сервер записує значення, JavaScript зчитує їх одразу при завантаженні.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.