# Ссылки между DOM-элементами Для того, чтобы изменить узел DOM или его содержимое, нужно сначала его получить. Доступ к DOM начинается с объекта `document`. Из него можно добраться до любых узлов. [cut] Вот картина, которая, с небольшими дополниниями, будет обсуждаться в этой главе: ## Корень: documentElement и body Войти в "корень" дерева можно двумя путями.
`` = `document.documentElement`
Первая точка входа -- `document.documentElement`. Это свойство ссылается на DOM-объект для тега `HTML`.
`` = `document.body`
Вторая точка входа -- `document.body`, который соответствует тегу `BODY`.
Оба варианта отлично работают. Но есть одна тонкость: **`document.body` может быть равен `null`**. Например, при доступе к `document.body` в момент обработки тега `HEAD`, то `document.body = null`. Это вполне логично, потому что `BODY` еще не существует. **Нельзя получить доступ к элементу, которого еще не существует в момент выполнения скрипта.** В следующем примере, первый `alert` выведет `null`: ```html ``` [smart header="В DOM активно используется `null`"] В мире DOM в качестве значения "нет такого элемента" или "узел не найден" используется не `undefined`, а `null`. [/smart] ## document.getElementById или просто id Если элементу назначен специальный атрибут `id`, то можно получить его прямо по переменной с именем из значения `id`. Например: ```html
Элемент
``` По стандарту, значение `id` должно быть уникально, то есть в документе может быть только один элемент с данным `id`. Это поведение соответствует [стандарту](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem). Оно существует, в первую очередь, для совместимости, как осколок далёкого прошлого и не очень приветствуется, поскольку использует глобальные переменные. Браузер пытается помочь нам, смешивая пространства имён JS и DOM, но при этом возможны конфликты. **Более правильной и общепринятой практикой является доступ к элементу вызовом `document.getElementById("идентификатор")`.** Например: ```html
Выделим этот элемент
``` **Далее я изредка буду использовать прямое обращение через переменную в примерах, чтобы было меньше букв и проще было понять происходящее. Но предпочтительным методом является `document.getElementById`.** ## Дочерние элементы Здесь и далее мы будем использовать два принципиально разных термина. ### childNodes Псевдо-массив `childNodes` хранит все дочерние элементы, включая текстовые. Пример ниже последовательно выведет дочерние элементы `document.body`: ```html ``` Во всех браузерах, кроме старых IE, `document.body.childNodes[0]` это текстовый узел из пробелов, а `DIV` -- второй потомок: `document.body.childNodes[1]`. В IE8- не создаются пустые текстовые узлы, поэтому там дети начнутся с `DIV`. Почему же перечисление узлов в примере выше заканчивается на `SCRIPT`? Неужели под скриптом нет пробельного узла? Да просто потому, что пробельный узел будет в *итоговом документе*, но его еще нет на момент выполнения скрипта. [warn header="Коллекция только для чтения!"] Все навигационные свойства, которые перечислены в этой главе -- только для чтения. Нельзя просто заменить элемент присвоением `childNodes[i] = ...`. В частности, методы массива для `childNodes` тоже не поддерживаются, поэтому это свойство и называют "коллекцией". Изменения DOM осуществляется другими методами, которые мы рассмотрим далее, все навигационные ссылки при этом обновляются автоматически. [/warn] ### children А что если текстовые узлы нам не интересны? **Свойство `children`, перечисляет только дочерние узлы-элементы, соответствующие тегам.** Модифицируем предыдущий пример, применив `children` вместо `childNodes`. Теперь он будет выводить не все узлы, а только узлы-элементы: ```html ``` [warn header="В IE8- в `children` присутствуют узлы-комментарии"] С точки зрения стандарта это ошибка, но IE8- также включает в `children` узлы, соответствующие HTML-комментариям. Это может привести к сюрпризам при использовании свойства `children`, поэтому HTML-комментарии либо убирают либо используют функцию (или фреймворк, к примеру, jQuery), который автоматически офильтрует их. [/warn] ### Коллекции -- не массивы Коллекции, которые возвращают методы поиска, не являются массивами. У них нет методов массива, таких как `join`, `pop`, `forEach` и т.п. Например, этот пример выполнится с ошибкой: ```js //+ run var elems = document.documentElement.childNodes; *!* elems.forEach(function(elem) { // нет такого метода! */!* /* ... */ }); ``` Можно для перебора коллекции использовать обычный цикл `for(var i=0; i
  • Применить метод массива через `call/apply`: ```js //+ run var elems = document.documentElement.childNodes; *!* [].forEach.call(elems, function(elem) { */!* alert(elem); // HEAD, текст, BODY }); ```
  • При помощи [Array.prototype.slice](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) сделать из коллекции массив. Обычно вызов `arr.slice(a, b)` делает новый массив и копирует туда элементы `arr` с индексами от `a` до `b-1` включительно. Если же вызвать его без аргументов `arr.slice()`, то он делает новый массив и копирует туда все элементы `arr`. Это работает и для коллекции: ```js //+ run var elems = document.documentElement.childNodes; *!* elems = Array.prototype.slice.call(elems); // теперь elems - массив */!* elems.forEach(function(elem) { alert(elem.tagName); // HEAD, текст, BODY }); ```
  • [warn header="Нельзя перебирать коллекцию через `for..in`"] Ранее мы говорили, что не рекомендуется использовать для перебора массива цикл `for..in`. **Коллекции -- наглядный пример, почему нельзя. Они похожи на массивы, но у них есть свои свойства и методы, которых в массивах нет.** При запуске этого кода вы увидите, что `alert` сработает не 3, а целых 5 раз! ```js //+ run var elems = document.documentElement.childNodes; for(var key in elems) { alert(key); // 0, 1, 2, length, item } ``` Цикл `for..in` вывел не только ожидаемые индексы `0`, `1`, `2`, по которым лежат узлы в коллекции, но и свойства `length` (в коллекции оно enumerable), а также функцию `item(n)` -- она никогда не используется, возвращает `n-й` элемент коллекции, старый аналог обращения по индексу `[n]`. В реальном коде мы хотим перебирать только элементы, поэтому желательно использовать `for(var i=0; i
    ...
    ...
    ``` DOM-дерево будет таким (внутренности `div` и `ul` скрыты):
    Если бы пробельных узлов не было, например, в IE8, то была бы такая картина ссылок: С другой стороны, так как пробельные узлы, всё же, есть, то `body.firstChild` и `body.lastChild` будут указывать как раз на них, то есть на первый и последний `#text`. Всегда верны равенства: ```js body.firstChild === body.childNodes[0] body.lastChild === body.childNodes[body.childNodes.length-1] ``` ## parentNode, previousSibling и nextSibling Ранее мы смотрели свойства для доступа к детям. Теперь рассмотрим ссылки для доступа вверх и в стороны от узла. Ниже изображены ссылки между `BODY` и его потомками для документа: ```html ``` Ссылки (пробельные узлы обозначены решеткой `#`): ## Ссылки для элементов (IE9+) Все современные браузеры, включая IE9+, поддерживают дополнительные ссылки: Любые другие узлы, кроме элементов, просто игнорируются. Например: ```html firstElementChild:
    ...
    lastElementChild: ... ``` Современные браузеры также поддерживают дополнительные интерфейсы для обхода DOM c фильтром по узлам: `NodeIterator`, `TreeFilter` и `TreeWalker`. Они были утверждены аж в 2000-м году, однако на практике оказались неудобными, и потому практически не применяются. Вы можете почитать о них в стандарте [DOM 2 Traversal](http://www.w3.org/TR/DOM-Level-2-Traversal-Range/traversal.html#Traversal-Filters). ## Итого Сверху в DOM можно войти либо через `document.documentElement` (тег `HTML`), либо через `document.body` (тег `BODY`). По элементу DOM можно получить всех соседей через ссылки:
    `childNodes`, `children`
    Список дочерних узлов.
    `firstChild`, `lastChild`
    Первый и последний потомки
    `parentNode`
    Родительский узел
    `previousSibling`, `nextSibling`
    Соседи влево-вправо
    Все навигационные ссылки доступны только для чтения и поддерживаются автоматически. Свойства-коллекции, хотя и имеют индексы, а также `length`, не являются массивами. Поэтому их и называют "псевдомассивами", "коллекциями" или "списками". Далее мы встретимся с кучей других полезных коллекций, которые тоже не будут массивами. В современных браузерах, включая IE9+, реализованы дополнительные свойства, работающие только для элементов: Картинка только со ссылками для элементов: [libs] d3 domtree [/libs]