Когда браузер загружает страницу, он «читает» (также говорят: «парсит») HTML и генерирует из него DOM-объекты. Для узлов-элементов большинство стандартных HTML-атрибутов автоматически становятся свойствами DOM-объектов.
Например, для такого тега <body id="page">
у DOM-объекта будет такое свойство body.id="page"
.
Но преобразование атрибута в свойство происходит не один-в-один! В этой главе мы уделим внимание различию этих двух понятий, чтобы посмотреть, как работать с ними, когда они одинаковые и когда разные.
DOM-свойства
Ранее мы уже видели встроенные DOM-свойства. Их много. Но технически нас никто не ограничивает, и если этого мало – мы можем добавить своё собственное свойство.
DOM-узлы – это обычные объекты JavaScript. Мы можем их изменять.
Например, создадим новое свойство для document.body
:
document.body.myData = {
name: 'Caesar',
title: 'Imperator'
};
alert(document.body.myData.title); // Imperator
Мы можем добавить и метод:
document.body.sayTagName = function() {
alert(this.tagName);
};
document.body.sayTagName(); // BODY (значением "this" в этом методе будет document.body)
Также можно изменять встроенные прототипы, такие как Element.prototype
и добавлять новые методы ко всем элементам:
Element.prototype.sayHi = function() {
alert(`Hello, I'm ${this.tagName}`);
};
document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm 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). Стандартные атрибуты описаны в спецификации для соответствующего класса элемента.
Мы можем увидеть это на примере ниже:
<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> checkbox
<script>
alert(input.getAttribute('checked')); // значение атрибута: пустая строка
alert(input.checked); // значение свойства: true
</script>
Есть и другие примеры. Атрибут style
– строка, но свойство style
является объектом:
<div id="div" style="color:red;font-size:120%">Hello</div>
<script>
// строка
alert(div.getAttribute('style')); // color:red;font-size:120%
// объект
alert(div.style); // [object CSSStyleDeclaration]
alert(div.style.color); // red
</script>
Хотя большинство свойств, всё же, строки.
При этом некоторые из них, хоть и строки, могут отличаться от атрибутов. Например, DOM-свойство href
всегда содержит полный URL, даже если атрибут содержит относительный URL или просто #hash
.
Ниже пример:
<a id="a" href="#hello">link</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: "Pete",
age: 25
};
for(let div of document.querySelectorAll('[show-info]')) {
// вставить соответствующую информацию в поле
let field = div.getAttribute('show-info');
div.innerHTML = user[field]; // сначала Pete в 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">
A new order.
</div>
<div class="order" order-state="pending">
A pending order.
</div>
<div class="order" order-state="canceled">
A canceled order.
</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
, становятся свойствами, записанными с помощью верблюжьей нотации: dataset.orderState
.
Вот переписанный пример «состояния заказа»:
<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, а нам может понадобиться получить «оригинальное» значение.