Коли браузер завантажує сторінку, він “читає” (іншими словами: “парсить”) HTML і генерує об’єкти з нього. Для вузлів-елементів, найбільш стандартні атрибути 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
// нестандартний атрибут не дає властивості
alert(document.body.something); // undefined
</script>
</body>
Зверніть увагу, що стандартний атрибут для одного елемента може бути невідомим для іншого. Наприклад, "type"
– це стандартний для <input>
(HTMLInputElement), але не для <body>
(HTMLBodyElement). Стандартні атрибути описані у специфікації для відповідного класу елемента.
Here we can see it:
<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')); // нестандартна
</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>
// string
alert(div.getAttribute('style')); // color:red;font-size:120%
// object
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-*
є валідним, безпечним способом передачі власних даних.
Зверніть увагу, що ми можемо не тільки читати, але й модифікувати атрибути даних. Тоді 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-адресою, і ми можемо бажати отримати “оригінальне” значення.
Коментарі
<code>
, для кількох рядків – обгорніть їх тегом<pre>
, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)