Коли браузер завантажує сторінку, він “читає” (іншими словами: “парсить”) HTML і генерує DOM об’єкти з нього. Для вузлів-елементів більшість стандартних атрибутів HTML автоматично стають властивостями об’єктів DOM.
Наприклад, якщо тег це <body id="page">
, тоді об’єкт DOM матиме body.id="page"
.
Але представлення атрибутів через властивості не відбувається один до одного! У цій главі ми звернемо увагу на відмінності цих двох концепцій, щоб навчитись, як з ними працювати, коли вони співпадають і коли відрізняються.
DOM властивості
Ми вже бачили вбудовані властивості DOM. Їх багато. Але технічно ніхто нас не обмежує, і якщо їх недостатньо, ми можемо додати власні.
DOM вузли є звичайними об’єктами JavaScript. Ми можемо змінювати їх.
Наприклад, створімо нову властивість у document.body
:
document.body.myData = {
name: 'Цезар',
title: 'Імператор'
};
alert(document.body.myData.title); // Імператор
Ми також можемо додати метод:
document.body.sayTagName = function() {
alert(this.tagName);
};
document.body.sayTagName(); // BODY (значення "this" у методі є document.body)
Ми також можемо змінювати вбудовані прототипи, такі як Element.prototype
і додати нові методи для всіх елементів:
Element.prototype.sayHi = function() {
alert(`Привіт, Я ${this.tagName}`);
};
document.documentElement.sayHi(); // Привіт, Я HTML
document.body.sayHi(); // Привіт, Я BODY
Отже, властивості та методи DOM поводяться так само, як і звичайні об’єкти JavaScript:
- Вони можуть мати будь-яке значення.
- Вони чутливі до регістру (наприклад
elem.nodeType
, неelem.NoDeTyPe
).
HTML атрибути
У HTML, теги можуть мати атрибути. Коли браузер аналізує HTML і створює DOM-об’єкти для тегів, він розпізнає стандартні атрибути та створює з них властивості DOM.
Отже, коли елемент має id
або інший стандартний атрибут, створюється відповідна властивість. Проте цього не відбувається, якщо атрибут не є стандартним.
Наприклад:
<body id="test" something="non-standard">
<script>
alert(document.body.id); // test
// нестандартний атрибут не створює DOM-властивості
alert(document.body.something); // undefined
</script>
</body>
Зверніть увагу, що стандартний атрибут для одного елемента може бути невідомим для іншого. Наприклад, "type"
– це стандартний для <input>
(HTMLInputElement), але не для <body>
(HTMLBodyElement). Стандартні атрибути описані у специфікації для відповідного класу елемента.
Ось приклад:
<body id="body" type="...">
<input id="input" type="text">
<script>
alert(input.type); // text
alert(body.type); // undefined: DOM властивість не створена, тому що вона нестандартна
</script>
</body>
Отже, якщо атрибут не є стандартним, то для нього не буде створено DOM-властивості. Чи є спосіб отримати доступ до таких атрибутів?
Звичайно. Всі атрибути доступні за допомогою наступних методів:
elem.hasAttribute(name)
– перевіряє наявність атрибута.elem.getAttribute(name)
– отримує значення атрибута.elem.setAttribute(name, value)
– встановлює значення атрибута.elem.removeAttribute(name)
– видаляє атрибут.
Ці методи працюють із значеннями, записаними у HTML.
Також можна прочитати всі атрибути, використовуючи elem.attributes
: це колекція об’єктів, які належать вбудованому класу Attr, і мають властивості name
та value
.
Ось демонстрація читання нестандартного атрибута:
<body something="non-standard">
<script>
alert(document.body.getAttribute('something')); // non-standard
</script>
</body>
Атрибути HTML мають такі особливості:
- Їх назва нечутлива до регістру (
id
– це те саме, що йID
). - Їхні значення завжди є рядками.
Ось розширена демонстрація роботи з атрибутами:
<body>
<div id="elem" about="Elephant"></div>
<script>
alert( elem.getAttribute('About') ); // (1) 'Elephant', читання
elem.setAttribute('Test', 123); // (2), запис
alert( elem.outerHTML ); // (3), дивимося чи атрибут знаходиться в HTML (так)
for (let attr of elem.attributes) { // (4) перерахування всіх атрибутів
alert( `${attr.name} = ${attr.value}` );
}
</script>
</body>
Будь ласка, зверніть увагу:
getAttribute('About')
– перша літера написана великою, а в HTML вона пишеться маленькою. Але це не має значення: імена атрибутів нечутливі до регістру.- Ми можемо присвоїти атрибуту будь-яке значення, але воно стане рядком. Тому тут ми отримуємо
"123"
як значення. - Всі атрибути, включаючи ті, які ми встановлюємо, видно в
outerHTML
. - Колекція
attributes
є ітерованою і має всі атрибути елемента (стандартні та нестандартні) у вигляді об’єктів з властивостямиname
таvalue
.
Синхронізація властивостей і атрибутів
Коли стандартний атрибут змінюється, відповідна властивість автоматично оновлюється і, за деякими винятками, навпаки.
У наведеному нижче прикладі id
модифікується як атрибут, і ми також можемо побачити, що властивість теж змінюється. А потім те ж саме навпаки:
<input>
<script>
let input = document.querySelector('input');
// атрибут => властивість
input.setAttribute('id', 'id');
alert(input.id); // id (оновлений)
// властивість => атрибут
input.id = 'newId';
alert(input.getAttribute('id')); // newId (оновлений)
</script>
Але є винятки, наприклад, input.value
синхронізується лише в одному напрямку, з атрибуту → до властивості, але не навпаки:
<input>
<script>
let input = document.querySelector('input');
// атрибут => властивість
input.setAttribute('value', 'text');
alert(input.value); // text
// НІ властивість => атрибут
input.value = 'newValue';
alert(input.getAttribute('value')); // text (не оновлено!)
</script>
У прикладі вище:
- Зміна атрибута
value
оновлює властивість. - Але зміна властивості не впливає на атрибут.
Ця “особливість” може стати в нагоді, оскільки дії користувача можуть призвести до змін value
, а потім після них, якщо ми хочемо відновити “оригінальне” значення з HTML, то воно є в атрибуті.
Властивості DOM типізовані
Властивості DOM не завжди є рядками. Наприклад, властивість input.checked
(для чекбоксів) має логічний(булевий) тип:
<input id="input" type="checkbox" checked> чекбокс
<script>
alert(input.getAttribute('checked')); // значення атрибута: порожній рядок
alert(input.checked); // значення властивостей є: true
</script>
Є й інші приклади. Атрибут style
– це рядок, але властивість style
є об’єктом:
<div id="div" style="color:red;font-size:120%">Привіт</div>
<script>
// рядок
alert(div.getAttribute('style')); // color:red;font-size:120%
// об’єкт
alert(div.style); // [object CSSStyleDeclaration]
alert(div.style.color); // red
</script>
Більшість властивостей є рядками.
Досить рідко, властивість в DOM може відрізнятися від атрибута, навіть якщо тип властивості в DOM – це рядок. Наприклад, властивість href
DOM завжди є повним URL, навіть якщо атрибут містить відносну URL-адресу або просто #hash
.
Ось приклад:
<a id="a" href="#hello">посилання</a>
<script>
// атрибут
alert(a.getAttribute('href')); // #hello
// властивість
alert(a.href ); // повний URL у формі http://site.com/page#hello
</script>
Якщо нам потрібна властивість href
або будь-якого іншого атрибуту саме так, як написано в HTML, ми можемо використовувати getAttribute
.
Нестандартні атрибути, dataset
При написанні HTML ми використовуємо багато стандартних атрибутів. Але як щодо нестандартних, користувацьких? Спершу давайте побачимо, чи вони корисні, і для чого вони потрібні?
Іноді нестандартні атрибути використовуються для передачі користувацьких даних з HTML до JavaScript, або для “позначення” HTML-елементів для JavaScript.
Наприклад:
<!-- позначимо div, щоб показати поле "name" тут -->
<div show-info="name"></div>
<!-- і вік "age" тут -->
<div show-info="age"></div>
<script>
// код знаходить елемент з позначкою і показує те, що запитується
let user = {
name: "Іван",
age: 25
};
for(let div of document.querySelectorAll('[show-info]')) {
// вставимо відповідну інформацію в поле
let field = div.getAttribute('show-info');
div.innerHTML = user[field]; // по-перше, значення "Іван" в div з "name", потім 25 в "age"
}
</script>
Також вони можуть бути використані для стилізації елемента.
Наприклад, тут для замовлення використовується атрибут order-state
:
<style>
/* стилі покладаються на користувальницький атрибут "order-state" */
.order[order-state="new"] {
color: green;
}
.order[order-state="pending"] {
color: blue;
}
.order[order-state="canceled"] {
color: red;
}
</style>
<div class="order" order-state="new">
Нове замовлення.
</div>
<div class="order" order-state="pending">
Замовлення очікується.
</div>
<div class="order" order-state="canceled">
Скасоване замовлення.
</div>
Чому б використовувати атрибут краще ніж мати класи .order-state-new
, .order-state-pending
, .order-state-canceled
?
Оскільки атрибут більш зручний для управління. Стан може бути легко змінено, наприклад:
// трохи простіше, ніж видалення старого/додавання нового класу
div.setAttribute('order-state', 'canceled');
Але може виникнути потенційна проблема з користувацькими атрибутами. Що робити, якщо ми використовуємо нестандартний атрибут для наших цілей, а потім стандарт вводить його та надає йому якусь функціональність? Мова HTML жива, вона зростає, а також з’являється більше атрибутів, які відповідають потребам розробників. У такому випадку можуть виникнути неочікувані наслідки.
Щоб уникнути конфліктів, існують data-* атрибути.
Всі атрибути, які починаються з “data-” зарезервовані для використання програмістами. Вони доступні у властивості dataset
.
Наприклад, якщо elem
має атрибут, що називається "data-about"
, то він доступний як elem.dataset.about
.
Наприклад:
<body data-about="Elephants">
<script>
alert(document.body.dataset.about); // Elephants
</script>
Атрибути, що складаються з декількох слів, такі як data-order-state
стають записані за допомогою верблюжої нотації (camel-case): dataset.orderState
.
Ось приклад переписаного “order state”:
<style>
.order[data-order-state="new"] {
color: green;
}
.order[data-order-state="pending"] {
color: blue;
}
.order[data-order-state="canceled"] {
color: red;
}
</style>
<div id="order" class="order" data-order-state="new">
A new order.
</div>
<script>
// читання
alert(order.dataset.orderState); // new
// зміна
order.dataset.orderState = "pending"; // (*)
</script>
Використання атрибутів data-*
є валідним, безпечним способом передачі додаткових даних.
Зверніть увагу, що ми можемо не тільки читати, але й модифікувати data-атрибути. Тоді CSS відповідно оновлює зовнішній вигляд: у прикладі вище останнього рядка (*)
змінює колір на синій.
Підсумки
- Атрибути – це те, що написано в HTML.
- Властивості – це те, що є в об’єктах DOM.
Невелике порівняння:
Властивості | Атрибути | |
---|---|---|
Тип | Будь-яке значення, стандартні властивості мають типи, описані в специфікації | Рядок |
Назва | Назва є чутливою до регістру | Назва не чутлива до регістру |
Методи роботи з атрибутами:
elem.hasAttribute(name)
– перевірити наявність.elem.getAttribute(name)
– отримати значення.elem.setAttribute(name, value)
– встановити значення.elem.removeAttribute(name)
– видалити атрибут.elem.attributes
– це колекція всіх атрибутів.
Для більшості ситуацій краще використовувати властивості DOM. Ми повинні посилатися на атрибути лише тоді, коли DOM властивості не підходять нам, коли нам потрібні саме атрибути, наприклад:
- Нам потрібен нестандартний атрибут. Але якщо він починається з
data-
, то ми повинні використовуватиdataset
. - Ми хочемо прочитати значення “як написано” у HTML. Значення DOM властивості може бути іншим, наприклад, властивість
href
завжди є повною URL-адресою, і ми можемо бажати отримати “оригінальне” значення.