DOM-дерево
В соответствии с объектной моделью документа («Document Object Model», коротко DOM), каждый HTML-тег является объектом. Вложенные теги являются «детьми» родительского элемента. Текст, который находится внутри тега, также является объектом.
Все эти объекты доступны при помощи JavaScript, мы можем использовать их для изменения страницы.
Например, document.body – объект для тега .
Если запустить этот код, то станет красным на 3 секунды:
document.body.style.background = 'red'; // сделать фон красным setTimeout(() => document.body.style.background = '', 3000); // вернуть назад
Это был лишь небольшой пример того, что может DOM. Скоро мы изучим много способов работать с DOM, но сначала нужно познакомиться с его структурой.
Пример DOM
Начнём с такого, простого, документа:
О лосях Правда о лосях.
DOM – это представление HTML-документа в виде дерева тегов. Вот как оно выглядит:
На рисунке выше узлы-элементы можно кликать, и их дети будут скрываться и раскрываться.
Каждый узел этого дерева – это объект.
Теги являются узлами-элементами (или просто элементами). Они образуют структуру дерева: – это корневой узел, и его дочерние узлы и т.д.
Текст внутри элементов образует текстовые узлы, обозначенные как #text . Текстовый узел содержит в себе только строку текста. У него не может быть потомков, т.е. он находится всегда на самом нижнем уровне.
Например, в теге есть текстовый узел «О лосях» .
Обратите внимание на специальные символы в текстовых узлах:
- перевод строки: ↵ (в JavaScript он обозначается как \n )
- пробел: ␣
Пробелы и переводы строки – это полноправные символы, как буквы и цифры. Они образуют текстовые узлы и становятся частью дерева DOM. Так, в примере выше в теге есть несколько пробелов перед , которые образуют текстовый узел #text (он содержит в себе только перенос строки и несколько пробелов).
Существует всего два исключения из этого правила:
- По историческим причинам пробелы и перевод строки перед тегом игнорируются
- Если мы записываем что-либо после закрывающего тега , браузер автоматически перемещает эту запись в конец body , поскольку спецификация HTML требует, чтобы всё содержимое было внутри . Поэтому после закрывающего тега не может быть никаких пробелов.
В остальных случаях всё просто – если в документе есть пробелы (или любые другие символы), они становятся текстовыми узлами дерева DOM, и если мы их удалим, то в DOM их тоже не будет.
Здесь пробельных текстовых узлов нет:
О лосях Правда о лосях.
Пробелы по краям строк и пробельные текстовые узлы скрыты в инструментах разработки
Когда мы работаем с деревом DOM, используя инструменты разработчика в браузере (которые мы рассмотрим позже), пробелы в начале/конце текста и пустые текстовые узлы (переносы строк) между тегами обычно не отображаются.
Таким образом инструменты разработки экономят место на экране.
В дальнейших иллюстрациях DOM мы также будем для краткости пропускать пробельные текстовые узлы там, где они не имеют значения. Обычно они не влияют на то, как отображается документ.
Автоисправление
Если браузер сталкивается с некорректно написанным HTML-кодом, он автоматически корректирует его при построении DOM.
Например, в начале документа всегда должен быть тег . Даже если его нет в документе – он будет в дереве DOM, браузер его создаст. То же самое касается и тега .
Например, если HTML-файл состоит из единственного слова «Привет» , браузер обернёт его в теги и , добавит необходимый тег , и DOM будет выглядеть так:
При генерации DOM браузер самостоятельно обрабатывает ошибки в документе, закрывает теги и так далее.
Есть такой документ с незакрытыми тегами:
…Но DOM будет нормальным, потому что браузер сам закроет теги и восстановит отсутствующие детали:
Таблицы всегда содержат
Важный «особый случай» – работа с таблицами. По стандарту DOM у них должен быть , но в HTML их можно написать (официально) без него. В этом случае браузер добавляет в DOM самостоятельно.
Для такого HTML:
DOM-структура будет такой:
Видите? Из пустоты появился , как будто документ и был таким. Важно знать об этом, иначе при работе с таблицами возможны сюрпризы.
Другие типы узлов
Есть и некоторые другие типы узлов, кроме элементов и текстовых узлов.
Здесь мы видим узел нового типа – комментарий, обозначенный как #comment , между двумя текстовыми узлами.
Казалось бы – зачем комментарий в DOM? Он никак не влияет на визуальное отображение. Но есть важное правило: если что-то есть в HTML, то оно должно быть в DOM-дереве.
Все, что есть в HTML, даже комментарии, является частью DOM.
Даже директива , которую мы ставим в начале HTML, тоже является DOM-узлом. Она находится в дереве DOM прямо перед . Мы не будем рассматривать этот узел, мы даже не рисуем его на наших диаграммах, но он существует.
Даже объект document , представляющий весь документ, формально является DOM-узлом.
Существует 12 типов узлов. Но на практике мы в основном работаем с 4 из них:
- document – «входная точка» в DOM.
- узлы-элементы – HTML-теги, основные строительные блоки.
- текстовые узлы – содержат текст.
- комментарии – иногда в них можно включить информацию, которая не будет показана, но доступна в DOM для чтения JS.
Поэкспериментируйте сами
Чтобы посмотреть структуру DOM в реальном времени, попробуйте Live DOM Viewer. Просто введите что-нибудь в поле, и ниже вы увидите, как меняется DOM.
Другой способ исследовать DOM – это использовать инструменты разработчика браузера. Это то, что мы каждый день делаем при разработке.
Для этого откройте страницу elks.html, включите инструменты разработчика и перейдите на вкладку Elements.
Выглядит примерно так:
Вы можете увидеть DOM, понажимать на элементы, детально рассмотреть их и так далее.
Обратите внимание, что структура DOM в инструментах разработчика отображается в упрощённом виде. Текстовые узлы показаны как простой текст. И кроме пробелов нет никаких «пустых» текстовых узлов. Ну и отлично, потому что большую часть времени нас будут интересовать узлы-элементы.
Клик по этой кнопке в левом верхнем углу инспектора позволяет при помощи мыши (или другого устройства ввода) выбрать элемент на веб-странице и «проинспектировать» его (браузер сам найдёт и отметит его во вкладке Elements). Этот способ отлично подходит, когда у нас огромная HTML-страница (и соответствующий ей огромный DOM), и мы хотим увидеть, где находится интересующий нас элемент.
Есть и другой способ сделать это: можно кликнуть на странице по элементу правой кнопкой мыши и в контекстном меню выбрать «Inspect».
В правой части инструментов разработчика находятся следующие подразделы:
- Styles – здесь мы видим CSS, применённый к текущему элементу: правило за правилом, включая встроенные стили (выделены серым). Почти всё можно отредактировать на месте, включая размеры, внешние и внутренние отступы.
- Computed – здесь мы видим итоговые CSS-свойства элемента, которые он приобрёл в результате применения всего каскада стилей (в том числе унаследованные свойства и т.д.).
- Event Listeners – в этом разделе мы видим обработчики событий, привязанные к DOM-элементам (мы поговорим о них в следующей части учебника).
- … и т.д.
Лучший способ изучить инструменты разработчика – это прокликать их. Большинство значений можно менять и тут же смотреть результат.
Взаимодействие с консолью
При работе с DOM нам часто требуется применить к нему JavaScript. Например: получить узел и запустить какой-нибудь код для его изменения, чтобы посмотреть результат. Вот несколько подсказок, как перемещаться между вкладками Elements и Console.
- На вкладке Elements выберите первый элемент
- .
- Нажмите Esc – прямо под вкладкой Elements откроется Console.
Последний элемент, выбранный во вкладке Elements, доступен в консоли как $0 ; предыдущий, выбранный до него, как $1 и т.д.
Теперь мы можем запускать на них команды. Например $0.style.background = ‘red’ сделает выбранный элемент красным, как здесь:
Это мы посмотрели как получить узел из Elements в Console.
Есть и обратный путь: если есть переменная node , ссылающаяся на DOM-узел, можно использовать в консоли команду inspect(node) , чтобы увидеть этот элемент во вкладке Elements.
Или мы можем просто вывести DOM-узел в консоль и исследовать «на месте», как document.body ниже:
Это может быть полезно для отладки. В следующей главе мы рассмотрим доступ и изменение DOM при помощи JavaScript.
Инструменты разработчика браузера отлично помогают в разработке: мы можем исследовать DOM, пробовать с ним что-то делать и смотреть, что идёт не так.
Итого
HTML/XML документы представлены в браузере в виде DOM-дерева.
- Теги становятся узлами-элементами и формируют структуру документа.
- Текст становится текстовыми узлами.
- … и т.д. Всё, что записано в HTML, есть и в DOM-дереве, даже комментарии.
Для изменения элементов или проверки DOM-дерева мы можем использовать инструменты разработчика в браузере.
Здесь мы рассмотрели основы, наиболее часто используемые и важные действия для начала разработки. Подробную документацию по инструментам разработки Chrome Developer Tools можно найти на странице https://developers.google.com/web/tools/chrome-devtools. Лучший способ изучить инструменты – походить по разным вкладкам, почитать меню: большинство действий очевидны для пользователя. Позже, когда вы немного их изучите, прочитайте документацию и узнайте то, что осталось.
У DOM-узлов есть свойства и методы, которые позволяют выбирать любой из элементов, изменять, перемещать их на странице и многое другое. Мы вернёмся к ним в последующих разделах.
Атрибуты и DOM-свойства
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/dom-attributes-and-properties.
При чтении HTML браузер генерирует DOM-модель. При этом большинство стандартных HTML-атрибутов становятся свойствами соответствующих объектов.
Например, если тег выглядит как , то у объекта будет свойство body.id = «page» .
Но это преобразование – не один-в-один. Бывают ситуации, когда атрибут имеет одно значение, а свойство – другое. Бывает и так, что атрибут есть, а свойства с таким названием не создаётся.
Если коротко – HTML-атрибуты и DOM-свойства обычно, но не всегда соответствуют друг другу, нужно понимать, что такое свойство и что такое атрибут, чтобы работать с ними правильно.
Свои DOM-свойства
Ранее мы видели некоторые встроенные свойства DOM-узлов. Но, технически, никто нас ими не ограничивает.
Узел DOM – это объект, поэтому, как и любой объект в JavaScript, он может содержать пользовательские свойства и методы.
Например, создадим в document.body новое свойство и запишем в него объект:
document.body.myData = < name: 'Пётр', familyName: 'Петрович' >; alert( document.body.myData.name ); // Пётр
Можно добавить и новую функцию:
document.body.sayHi = function() < alert( this.nodeName ); >document.body.sayHi(); // BODY, выполнилась с правильным this
Нестандартные свойства и методы видны только в JavaScript и никак не влияют на отображение соответствующего тега.
Обратим внимание, пользовательские DOM-свойства:
- Могут иметь любое значение.
- Названия свойств чувствительны к регистру.
- Работают за счёт того, что DOM-узлы являются объектами JavaScript.
Атрибуты
Элементам DOM, с другой стороны, соответствуют HTML-теги, у которых есть текстовые атрибуты.
Конечно, здесь речь именно об узлах-элементах, не о текстовых узлах или комментариях.
Доступ к атрибутам осуществляется при помощи стандартных методов:
- elem.hasAttribute(name) – проверяет наличие атрибута
- elem.getAttribute(name) – получает значение атрибута
- elem.setAttribute(name, value) – устанавливает атрибут
- elem.removeAttribute(name) – удаляет атрибут
Эти методы работают со значением, которое находится в HTML.
Также все атрибуты элемента можно получить с помощью свойства elem.attributes , которое содержит псевдо-массив объектов типа Attr.
В отличие от свойств, атрибуты:
- Всегда являются строками.
- Их имя нечувствительно к регистру (ведь это HTML)
- Видны в innerHTML (за исключением старых IE)
Рассмотрим отличия между DOM-свойствами и атрибутами на примере HTML-кода:
Пример ниже устанавливает атрибуты и демонстрирует их особенности.
alert( elem.getAttribute('About') ); // (1) 'Elephant', атрибут получен elem.setAttribute('Test', 123); // (2) атрибут Test установлен alert( document.body.innerHTML ); // (3) в HTML видны все атрибуты! var attrs = elem.attributes; // (4) можно получить коллекцию атрибутов for (var i = 0; i
При запуске кода выше обратите внимание:
- getAttribute(‘About’) – первая буква имени атрибута About написана в верхнем регистре, а в HTML – в нижнем, но это не имеет значения, так как имена нечувствительны к регистру.
- Мы можем записать в атрибут любое значение, но оно будет превращено в строку. Объекты также будут автоматически преобразованы.
- После добавления атрибута его можно увидеть в innerHTML элемента.
- Коллекция attributes содержит все атрибуты в виде объектов со свойствами name и value .
Когда полезен доступ к атрибутам?
Когда браузер читает HTML и создаёт DOM-модель, то он создаёт свойства для всех стандартных атрибутов.
Например, свойства тега ‘A’ описаны в спецификации DOM: HTMLAnchorElement.
Например, у него есть свойство «href» . Кроме того, он имеет «id» и другие свойства, общие для всех элементов, которые описаны в спецификации в HTMLElement.
Все стандартные свойства DOM синхронизируются с атрибутами, однако не всегда такая синхронизация происходит 1-в-1, поэтому иногда нам нужно значение именно из HTML, то есть атрибут.
Рассмотрим несколько примеров.
Ссылка «как есть» из атрибута href
Синхронизация не гарантирует одинакового значения в атрибуте и свойстве.
Для примера, посмотрим, что произойдёт с атрибутом «href» при изменении свойства:
Это происходит потому, что атрибут может быть любым, а свойство href , в соответствии со спецификацией W3C, должно быть полной ссылкой.
Стало быть, если мы хотим именно то, что в HTML, то нужно обращаться через атрибут.
Есть и другие подобные атрибуты
Кстати, есть и другие атрибуты, которые не копируются в точности. Например, DOM-свойство input.checked имеет логическое значение true/false , а HTML-атрибут checked – любое строковое, важно лишь его наличие.
Работа с checked через атрибут и свойство: