DOM дозволяє нам робити будь-що з елементами та їх вмістом, але спочатку нам потрібно отримати відповідний DOM об’єкт.
Усі операції з DOM починаються з об’єкта document
. Це головна “точка входу” в DOM. З нього ми можемо отримати доступ до будь-якого вузла.
Ось так виглядають основні посилання, які дозволяють переміщатися між вузлами DOM:
Обговоримо їх більш детально.
Зверху: documentElement і body
Найвищі вузли дерева доступні безпосередньо як властивості document
:
<html>
=document.documentElement
- Найвищий вузол документа –
document.documentElement
. В DOM він відповідає тегу<html>
. <body>
=document.body
- Іншим широко використовуваним вузлом DOM є елемент
<body>
–document.body
. <head>
=document.head
- Тег
<head>
доступний якdocument.head
.
document.body
може бути null
Скрипт не може отримати доступ до елемента, який не існує на момент виконання цього скрипта.
Зокрема, якщо скрипт знаходиться всередині <head>
, то document.body
недоступний, оскільки браузер ще не прочитав його.
Отже, у прикладі нижче перший alert
виведе null
:
<html>
<head>
<script>
alert( "Якщо у HEAD: " + document.body ); // null, ще немає <body>
</script>
</head>
<body>
<script>
alert( "Якщо у BODY: " + document.body ); // HTMLBodyElement, тепер body існує
</script>
</body>
</html>
null
означає “не існує”У DOM значення null
означає “не існує” або “такого вузла немає”.
Дочірні вузли: childNodes, firstChild, lastChild
Відтепер ми будемо використовувати два терміни:
- Дочірні вузли (або діти) – елементи, які є безпосередніми дітьми. Іншими словами, вони вкладені саме в цей вузол. Наприклад,
<head>
і<body>
є дочірніми елементами<html>
. - Нащадки – всі елементи, які вкладені в даний, включаючи дітей, їхніх дітей тощо.
Наприклад, тут <body>
має дочірні <div>
і <ul>
(і кілька пустих текстових вузлів):
<html>
<body>
<div>Begin</div>
<ul>
<li>
<b>Information</b>
</li>
</ul>
</body>
</html>
…А нащадками <body>
є не тільки прямі дочірні елементи <div>
, <ul>
, але й більш глибоко вкладені елементи, такі як <li>
(дочірній елемент <ul>
) та <b>
(дочірній елемент <li>
) – тобто усі елементи піддерева.
Колекція childNodes
містить список усіх дочірніх вузлів, включаючи текстові вузли.
Наведений нижче приклад показує дочірні елементи document.body
:
<html>
<body>
<div>Початок</div>
<ul>
<li>Інформація</li>
</ul>
<div>Кінець</div>
<script>
for (let i = 0; i < document.body.childNodes.length; i++) {
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
}
</script>
...щось ще...
</body>
</html>
Зверніть увагу на цікаву деталь. Якщо ми запустимо наведений вище приклад, останнім показаним елементом буде <script>
. Насправді нижче в документі є більше коду, але на момент виконання скрипту браузер його ще не прочитав, тому скрипт його не бачить.
Властивості firstChild
і lastChild
надають швидкий доступ до першого та останнього дочірнього вузла.
Це лише скорочення. Якщо існують дочірні вузли, то завжди вірно наступне:
elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
Існує також спеціальна функція elem.hasChildNodes()
, що перевіряє, чи є взагалі дочірні вузли.
DOM колекції
Як бачимо, childNodes
виглядає як масив. Але насправді це не масив, а скоріше колекція – спеціальний ітеративний об’єкт-псевдомасив.
Є два важливих наслідки з цього:
- Ми можемо використовувати
for..of
, щоб перебирати його:
for (let node of document.body.childNodes) {
alert(node); // показує всі вузли з колекції
}
Це працює, бо колекція є ітерованим об’єктом (є потрібний для цього метод Symbol.iterator
).
- Методи масиву не працюватимуть, бо колекція це не масив:
alert(document.body.childNodes.filter); // undefined (методу filter немає!)
Перший наслідок приємний, з другим можна змиритися, оскільки ми можемо використовувати Array.from
для створення “справжнього” масиву з колекції, якщо нам потрібні методи масиву:
alert( Array.from(document.body.childNodes).filter ); // function
Колекції DOM і навіть більше – всі властивості навігації, перелічені в цьому розділі, доступні лише для зчитування.
Ми не можемо замінити дочірній елемент на щось інше, призначивши childNodes[i] = ...
.
Для зміни DOM потрібні інші методи. Ми розберемо їх у наступному розділі.
Майже всі колекції DOM, за незначними винятками, є живими. Іншими словами, вони завжди відображають поточний стан DOM.
Якщо ми зберегли посилання на elem.childNodes
і після цього додамо/видалимо вузли в DOM, вони автоматично з’являться в колекції.
for..in
для перебору колекційКолекції можна перебирати за допомогою for..of
. Але іноді люди намагаються використовувати для цього for..in
.
Будь ласка, не треба. Цикл for..in
перебирає всі властивості без виключення. А колекції мають деякі “додаткові” рідко використовувані властивості, які ми зазвичай не хочемо отримувати:
<body>
<script>
// показує 0, 1, length, item, values і більше.
for (let prop in document.body.childNodes) alert(prop);
</script>
</body>
Сусіди та батьківський вузол
Сусіди, або сусідні вузли – це вузли, які є нащадками одного батька.
Наприклад, тут <head>
і <body>
є сусідами:
<html>
<head>...</head><body>...</body>
</html>
<body>
вважається “наступним” або сусідом “праворуч” для<head>
,<head>
вважається “попереднім” або сусідом “ліворуч” для<body>
.
Наступний сусід знаходиться у властивості nextSibling
, а попередній – у previousSibling
.
Батьківський вузол доступний як parentNode
.
Наприклад:
// батьком <body> є <html>
alert( document.body.parentNode === document.documentElement ); // true
// після <head> іде <body>
alert( document.head.nextSibling ); // HTMLBodyElement
// після <body> іде <head>
alert( document.body.previousSibling ); // HTMLHeadElement
Навігація лише за елементами
Властивості навігації, перераховані вище, відносяться до всіх вузлів в документі. Наприклад, у childNodes
ми можемо побачити як текстові вузли, так і вузли елементів і навіть вузли коментарів, якщо вони існують.
Але для багатьох задач нам не потрібні текстові вузли чи вузли коментарів. Ми хочемо маніпулювати вузлами елементів, які представляють теги та формують структуру сторінки.
Тож давайте розглянемо додатковий набір посилань, які враховують лише вузли-елементи:
Посилання подібні до наведених вище, лише із словом Element
всередині:
children
– колекція дітей, які є елементами.firstElementChild
,lastElementChild
– перший і останній дочірні елементи.previousElementSibling
,nextElementSibling
– сусідні елементи.parentElement
– батьківський елемент.
parentElement
? Чи може батько бути не елементом?Властивість parentElement
повертає батьківський елемент “element”, тоді як parentNode
повертає батьківський “будь-який вузол”. Ці властивості зазвичай однакові: обидві вони отримують батьківський елемент.
За винятком document.documentElement
:
alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null
Причина в тому, що кореневий вузол document.documentElement
(<html>
) має document
як батьківський. Але він має тип document
– це не елемент, тому parentNode
повертає його, а parentElement
ні.
Ця деталь може бути корисною, коли ми хочемо перейти від довільного елемента elem
до <html>
, але не до document
:
while(elem = elem.parentElement) { // ідемо вгору, поки не дійдемо до <html>
alert( elem );
}
Давайте змінимо один із прикладів вище: замінимо childNodes
на children
. Тепер він показує лише елементи:
<html>
<body>
<div>Begin</div>
<ul>
<li>Information</li>
</ul>
<div>End</div>
<script>
for (let elem of document.body.children) {
alert(elem); // DIV, UL, DIV, SCRIPT
}
</script>
...
</body>
</html>
Ще корисних властивостей: таблиці
До цих пір ми описали основні властивості для навігації.
Деякі типи елементів DOM можуть надавати додаткові властивості, специфічні для їх типу, для зручності.
Таблиці є чудовим прикладом цього і представляють особливо важливий випадок:
Елемент<table>
підтримує (на додаток до наведених вище) такі властивості:
table.rows
– колекція рядків<tr>
таблиці.table.caption/tHead/tFoot
– посилання на елементи<caption>
,<thead>
,<tfoot>
.table.tBodies
– колекція елементів<tbody>
(за стандартом може бути багато, але завжди буде принаймні один – навіть якщо його немає у вихідному HTML, браузер помістить його в DOM).
Елементи <thead>
, <tfoot>
, <tbody>
забезпечують властивість rows
:
tbody.rows
– колекція рядків<tr>
всередині.
<tr>
:
tr.cells
– колекція клітинок<td>
і<th>
всередині заданого рядка<tr>
.tr.sectionRowIndex
– позиція (індекс) заданого<tr>
всередині батьківського<thead>/<tbody>/<tfoot>
.tr.rowIndex
– номер (індекс) рядка<tr>
у таблиці в цілому (враховуючи всі рядки таблиці без виключення).
<td>
і <th>
:
td.cellIndex
– номер клітинки у рядку<tr>
.
Приклад використання:
<table id="table">
<tr>
<td>one</td><td>two</td>
</tr>
<tr>
<td>three</td><td>four</td>
</tr>
</table>
<script>
// отримати td з "two" (перший рядок, друга колонка)
let td = table.rows[0].cells[1];
td.style.backgroundColor = "red"; // виділити червоним
</script>
Специфікація: табличні дані.
Існують також додаткові властивості навігації для HTML-форм. Ми розглянемо їх пізніше, коли почнемо працювати з формами.
Підсумки
Для вузла DOM, ми можемо перейти до його безпосередніх сусідів за допомогою властивостей навігації.
Існує два основних набори:
- Для всіх вузлів:
parentNode
,childNodes
,firstChild
,lastChild
,previousSibling
,nextSibling
. - Лише для вузлів елементів:
parentElement
,children
,firstElementChild
,lastElementChild
,previousElementSibling
,nextElementSibling
.
Деякі типи елементів DOM (наприклад, таблиці) надають додаткові властивості та колекції для доступу до їх вмісту.
Коментарі
<code>
, для кількох рядків – обгорніть їх тегом<pre>
, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)