- Walking the DOM
- On top: documentElement and body
- Children: childNodes, firstChild, lastChild
- DOM collections
- Siblings and the parent
- Element-only navigation
- Навигация по DOM-элементам
- Сверху: documentElement и body
- Дети: childNodes, firstChild, lastChild
- DOM-коллекции
- Соседи и родитель
- Навигация только по элементам
Walking the DOM
The DOM allows us to do anything with elements and their contents, but first we need to reach the corresponding DOM object.
All operations on the DOM start with the document object. That’s the main “entry point” to DOM. From it we can access any node.
Here’s a picture of links that allow for travel between DOM nodes:
Let’s discuss them in more detail.
On top: documentElement and body
The topmost tree nodes are available directly as document properties:
= document.documentElement The topmost document node is document.documentElement . That’s the DOM node of the tag. = document.body Another widely used DOM node is the element – document.body . = document.head The tag is available as document.head .
A script cannot access an element that doesn’t exist at the moment of running.
In particular, if a script is inside , then document.body is unavailable, because the browser did not read it yet.
So, in the example below the first alert shows null :
In the DOM, the null value means “doesn’t exist” or “no such node”.
Children: childNodes, firstChild, lastChild
There are two terms that we’ll use from now on:
- Child nodes (or children) – elements that are direct children. In other words, they are nested exactly in the given one. For instance, and are children of element.
- Descendants – all elements that are nested in the given one, including children, their children and so on.
- but also more deeply nested elements, such as
(a child of
- ) and (a child of
) – the entire subtree.
The childNodes collection lists all child nodes, including text nodes.
The example below shows children of document.body :
Please note an interesting detail here. If we run the example above, the last element shown is . In fact, the document has more stuff below, but at the moment of the script execution the browser did not read it yet, so the script doesn’t see it.
Properties firstChild and lastChild give fast access to the first and last children.
They are just shorthands. If there exist child nodes, then the following is always true:
elem.childNodes[0] === elem.firstChild elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
There’s also a special function elem.hasChildNodes() to check whether there are any child nodes.
DOM collections
As we can see, childNodes looks like an array. But actually it’s not an array, but rather a collection – a special array-like iterable object.
There are two important consequences:
for (let node of document.body.childNodes) < alert(node); // shows all nodes from the collection >
That’s because it’s iterable (provides the Symbol.iterator property, as required).
alert(document.body.childNodes.filter); // undefined (there's no filter method!)
The first thing is nice. The second is tolerable, because we can use Array.from to create a “real” array from the collection, if we want array methods:
alert( Array.from(document.body.childNodes).filter ); // function
DOM collections, and even more – all navigation properties listed in this chapter are read-only.
We can’t replace a child by something else by assigning childNodes[i] = . .
Changing DOM needs other methods. We will see them in the next chapter.
Almost all DOM collections with minor exceptions are live. In other words, they reflect the current state of DOM.
If we keep a reference to elem.childNodes , and add/remove nodes into DOM, then they appear in the collection automatically.
Collections are iterable using for..of . Sometimes people try to use for..in for that.
Please, don’t. The for..in loop iterates over all enumerable properties. And collections have some “extra” rarely used properties that we usually do not want to get:
Siblings and the parent
Siblings are nodes that are children of the same parent.
For instance, here and are siblings:
- is said to be the “next” or “right” sibling of ,
- is said to be the “previous” or “left” sibling of .
The next sibling is in nextSibling property, and the previous one – in previousSibling .
The parent is available as parentNode .
// parent of is alert( document.body.parentNode === document.documentElement ); // true // after goes alert( document.head.nextSibling ); // HTMLBodyElement // before goes alert( document.body.previousSibling ); // HTMLHeadElement
Element-only navigation
Navigation properties listed above refer to all nodes. For instance, in childNodes we can see both text nodes, element nodes, and even comment nodes if they exist.
But for many tasks we don’t want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page.
So let’s see more navigation links that only take element nodes into account:
The links are similar to those given above, just with Element word inside:
- children – only those children that are element nodes.
- firstElementChild , lastElementChild – first and last element children.
- previousElementSibling , nextElementSibling – neighbor elements.
- parentElement – parent element.
The parentElement property returns the “element” parent, while parentNode returns “any node” parent. These properties are usually the same: they both get the parent.
With the one exception of document.documentElement :
alert( document.documentElement.parentNode ); // document alert( document.documentElement.parentElement ); // null
The reason is that the root node document.documentElement ( ) has document as its parent. But document is not an element node, so parentNode returns it and parentElement does not.
This detail may be useful when we want to travel up from an arbitrary element elem to , but not to the document :
while(elem = elem.parentElement) < // go up till alert( elem ); >
Let’s modify one of the examples above: replace childNodes with children . Now it shows only elements:
Навигация по DOM-элементам
DOM позволяет нам делать что угодно с элементами и их содержимым, но для начала нужно получить соответствующий DOM-объект.
Все операции с DOM начинаются с объекта document . Это главная «точка входа» в DOM. Из него мы можем получить доступ к любому узлу.
Так выглядят основные ссылки, по которым можно переходить между узлами DOM:
Поговорим об этом подробнее.
Сверху: documentElement и body
Самые верхние элементы дерева доступны как свойства объекта document :
= document.documentElement Самый верхний узел документа: document.documentElement . В DOM он соответствует тегу . = document.body Другой часто используемый DOM-узел – узел тега : document.body . = document.head Тег доступен как document.head .
Нельзя получить доступ к элементу, которого ещё не существует в момент выполнения скрипта.
В частности, если скрипт находится в , document.body в нём недоступен, потому что браузер его ещё не прочитал.
Поэтому, в примере ниже первый alert выведет null :
В DOM значение null значит «не существует» или «нет такого узла».
Дети: childNodes, firstChild, lastChild
Здесь и далее мы будем использовать два принципиально разных термина:
- Дочерние узлы (или дети) – элементы, которые являются непосредственными детьми узла. Другими словами, элементы, которые лежат непосредственно внутри данного. Например, и являются детьми элемента .
- Потомки – все элементы, которые лежат внутри данного, включая детей, их детей и т.д.
- (и несколько пустых текстовых узлов):
- и вложенные в них:
(ребёнок
- ) и (ребёнок
) – в общем, все элементы поддерева.
Коллекция childNodes содержит список всех детей, включая текстовые узлы.
Пример ниже последовательно выведет детей document.body :
Обратим внимание на маленькую деталь. Если запустить пример выше, то последним будет выведен элемент . На самом деле, в документе есть ещё «какой-то HTML-код», но на момент выполнения скрипта браузер ещё до него не дошёл, поэтому скрипт не видит его.
Свойства firstChild и lastChild обеспечивают быстрый доступ к первому и последнему дочернему элементу.
Они, по сути, являются всего лишь сокращениями. Если у тега есть дочерние узлы, условие ниже всегда верно:
elem.childNodes[0] === elem.firstChild elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
Для проверки наличия дочерних узлов существует также специальная функция elem.hasChildNodes() .
DOM-коллекции
Как мы уже видели, childNodes похож на массив. На самом деле это не массив, а коллекция – особый перебираемый объект-псевдомассив.
И есть два важных следствия из этого:
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 ); // сделали массив
DOM-коллекции, и даже более – все навигационные свойства, перечисленные в этой главе, доступны только для чтения.
Мы не можем заменить один дочерний узел на другой, просто написав childNodes[i] = . .
Для изменения DOM требуются другие методы. Мы увидим их в следующей главе.
Почти все DOM-коллекции, за небольшим исключением, живые. Другими словами, они отражают текущее состояние DOM.
Если мы сохраним ссылку на elem.childNodes и добавим/удалим узлы в DOM, то они появятся в сохранённой коллекции автоматически.
Коллекции перебираются циклом for..of . Некоторые начинающие разработчики пытаются использовать для этого цикл for..in .
Не делайте так. Цикл for..in перебирает все перечисляемые свойства. А у коллекций есть некоторые «лишние», редко используемые свойства, которые обычно нам не нужны:
Соседи и родитель
Соседи – это узлы, у которых один и тот же родитель.
- говорят, что – «следующий» или «правый» сосед
- также можно сказать, что «предыдущий» или «левый» сосед .
Следующий узел того же родителя (следующий сосед) – в свойстве nextSibling , а предыдущий – в previousSibling .
Родитель доступен через parentNode .
// родителем является alert( document.body.parentNode === document.documentElement ); // выведет true // после идёт alert( document.head.nextSibling ); // HTMLBodyElement // перед находится alert( document.body.previousSibling ); // HTMLHeadElement
Навигация только по элементам
Навигационные свойства, описанные выше, относятся ко всем узлам в документе. В частности, в childNodes находятся и текстовые узлы и узлы-элементы и узлы-комментарии, если они есть.
Но для большинства задач текстовые узлы и узлы-комментарии нам не нужны. Мы хотим манипулировать узлами-элементами, которые представляют собой теги и формируют структуру страницы.
Поэтому давайте рассмотрим дополнительный набор ссылок, которые учитывают только узлы-элементы:
Эти ссылки похожи на те, что раньше, только в ряде мест стоит слово Element :
- children – коллекция детей, которые являются элементами.
- firstElementChild , lastElementChild – первый и последний дочерний элемент.
- previousElementSibling , nextElementSibling – соседи-элементы.
- parentElement – родитель-элемент.
Свойство parentElement возвращает родитель-элемент, а parentNode возвращает «любого родителя». Обычно эти свойства одинаковы: они оба получают родителя.
За исключением document.documentElement :
alert( document.documentElement.parentNode ); // выведет document alert( document.documentElement.parentElement ); // выведет null
Причина в том, что родителем корневого узла document.documentElement ( ) является document . Но document – это не узел-элемент, так что parentNode вернёт его, а parentElement нет.
Эта деталь может быть полезна, если мы хотим пройти вверх по цепочке родителей от произвольного элемента elem к , но не до document :
while(elem = elem.parentElement) < // идти наверх до alert( elem ); >
Изменим один из примеров выше: заменим childNodes на children . Теперь цикл выводит только элементы: