На этом занятии
рассмотрим способы навигации по DOM дереву и выбора нужного элемента в HTML-документе.
Предположим, что у нас имеется вот такая страница:
<!DOCTYPE html>
<html>
<head>
<title>Уроки по JavaScript</title>
</head>
<body>
Абзац
<ul>
<li>Элемент списка</li>
</ul>
</body>
</html>
Мы не будем
рисовать DOM-дерево этого
документа, вы теперь можете делать это самостоятельно и, к тому же, сейчас в
этом нет необходимости. Как мы говорили на предыдущем занятии, точкой входа для
DOM является объект
document. Чтобы с его
помощью выбрать теги-объекты <html>, <head> и <body>,
используются следующие свойства:
let html = document.documentElement;
let body = document.body;
let head = document.head;
console.log(html, body, head);
Для наглядности
я буду писать программы непосредственно в тегах <script></script>, которые
пропишем в конце документа:
Обратите
внимание, что мы прописали тег <script> в конце
документа. Это сделано для того, чтобы браузер успел загрузить весь документ и
создать полное DOM-дерево. Если мы его подключим, например, в разделе head, то переменная body будет равна null (что означает,
что объект не существует). Вот это следует учитывать при работе с DOM: нужно быть
уверенным, что браузер полностью загрузил документ, прежде чем работать с ним
через JavaScript.
Дочерние узлы и потомки
Теперь
посмотрим, как можно перебирать вложенные элементы этого дерева. Для начала
введем два термина, чтобы мы понимали друг друга:
-
дочерние
узлы
(или дети) – элементы, которые являются непосредственными детьми узла;
-
потомки – все элементы,
которые лежат внутри данного, включая детей, детей их детей и т.д.
Например, вот в
этом фрагменте дерева head является дочерним узлом html, но title уже им не
является, так как вложен в head. А вот потомки – это все вложенные
элементы, значит, для html потомками будут и head и title. Вот это и
гласят данные определения.
Для каждого узла
дерева список его дочерних элементов можно получить через коллекцию childNodes, например, так:
for (let i = 0; i < document.body.childNodes.length; i++) {
console.log( document.body.childNodes[i] );
}
Коллекция childNodes представляет
собой псевдомассив, то есть, это итерируемый объект, но в нем отсутствуют
методы массива. Из предыдущих занятий мы знаем, что для перебора элементов
псевдомассива лучше использовать цикл for of:
for (let child of document.body.childNodes) {
console.log( child );
}
Некоторые
начинающим программисты применяют цикл for in для этой
коллекции, что не стоит делать, по крайней мере, по двум причинам: это будет
работать медленнее; будут доступны дополнительные свойства childNodes, которые обычно
не нужны.
Если же мы хотим
оперировать коллекцией childNodes как массивом, то ее можно
преобразовать в массив:
let arr = Array.from(document.body.childNodes);
и, затем,
использовать любые методы массивов, например:
arr.forEach((elem) => console.log(elem));
У каждого узла DOM-дерева имеются
свойства firstChild и lastChild. Они обеспечивают доступ к
первому и последнему дочернему элементу в списке childNodes. Например, так:
let html = document.documentElement;
let first = html.firstChild;
let last = html.lastChild;
console.log( first ); //head
console.log( last ); //body
По сути, это то
же самое, что и такой выбор:
let html = document.documentElement;
let first = html.childNodes[0];
let last = html.childNodes[html.childNodes.length-1];
console.log( first );
console.log( last );
но записано в
более краткой форме.
Если нам нужно
узнать, есть ли у текущего элемента дочерние узлы, то используется метод hasChildNodes():
let html = document.documentElement;
console.log( html.hasChildNodes() );
Возвращает true, если есть и false, если нет.
Обратите
внимание, что элементы коллекции childNodes доступны только для чтения.
Менять их, используя синтаксис:
document.body.childNodes[0] = document.head.childNodes[0];
нельзя. Для
удаления, добавления и изменения узлов дерева используются свои методы, о
которых речь пойдет позже.
Наконец, если
нам нужен только список дочерних тегов, то вместо коллекции childNodes используется
коллекция children:
for (let child of document.documentElement.children) {
console.log( child );
}
Теперь, мы видим
только список тегов.
Соседние элементы, родитель
Теперь поговорим
о переходах между соседними элементами. И введем такое понятие:
Соседи – это узлы, у
которых один и тот же родитель.
Например, вот в
такой базовой структуре документа:
<html>
<head>...</head><body>...</body>
</html>
объекты head и body являются
соседями, т.к. у них общий родитель – объект html. Причем, для head объект body является следующим
правым, а для body объект head – предыдущий
слева. Отсюда название двух свойств:
-
nextSibling
– следующий сосед
(справа);
-
previousSibling
– предыдущий сосед (слева).
Например
(структура HTML-документа
должна быть как выше):
console.log(document.head.nextSibling);
console.log(document.body.previousSibling);
В консоле увидим
объект body и head.
Наконец, если
нам нужно получить объект родителя, то используется свойство parentNode:
console.log(document.body.parentNode);
Итак,
относительно любого узла DOM-дерева можно осуществлять следующие
переходы:
Если же нам
нужны только узлы-элементы (как правило, это теги HTML-документа), то
используется похожий набор свойств:
Здесь есть
только одно ключевое отличие этих свойств (а, точнее, одного свойства parentElement), от ранее
рассмотренных. В целом и parentNode и parentElement возвращают
всегда один и тот же родительский элемент, кроме корневого элемента:
let html = document.documentElement;
console.log( html.parentNode ); // выведет document
console.log( html.parentElement ); // выведет null
Объект document не считается
элементом HTML-страницы,
поэтому ссылка parentElement для корня DOM-дерева
возвращает null.
Некоторые типы
DOM-элементов предоставляют для удобства дополнительные свойства, специфичные
для их типа. Например, объект table имеет такие дополнительные свойства:
-
table.rows
– коллекция строк <tr> таблицы;
-
table.caption/tHead/tFoot
– ссылки на элементы таблицы
<caption>, <thead>, <tfoot>;
-
table.tBodies
– коллекция элементов таблицы <tbody> (по спецификации их может быть
больше одного).
Объекты tr имеют такие
свойства:
-
tr.cells
– коллекция <td> и <th> ячеек, находящихся внутри строки
<tr>;
-
tr.sectionRowIndex
– номер строки <tr> в текущей секции
<thead>/<tbody>/<tfoot>;
-
tr.rowIndex
– номер строки <tr> в таблице (включая все строки таблицы).
И объект td:
-
td.cellIndex
– номер ячейки в строке <tr>.
Они используются
также как и описанные выше другие свойства узлов DOM-дерева.
Например, если у нас имеется вот такая таблица:
<table id="table_digs">
<tr>
<td>один</td><td>два</td>
</tr>
</table>
То можно
обратиться к ячейке со строкой два и вывести ее содержимое в консоль:
console.log( table_digs.rows[0].cells[1].innerHTML );
Здесь свойство innerHTML содержит HTML-текст выбранной
ячейки таблицы. О нем речь пойдет позже в других занятиях.