Коли браузер завантажує сторінку, він “читає” (іншими словами: “парсить”) 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-адресою, і ми можемо бажати отримати “оригінальне” значення.
Коментарі
<code>, для кількох рядків – обгорніть їх тегом<pre>, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)