Свойства узлов: тип, тег и содержимое
Теперь давайте более внимательно взглянем на DOM-узлы.
В этой главе мы подробнее разберём, что они собой представляют и изучим их основные свойства.
Классы DOM-узлов
У разных DOM-узлов могут быть разные свойства. Например, у узла, соответствующего тегу <a> , есть свойства, связанные со ссылками, а у соответствующего тегу <input> – свойства, связанные с полем ввода и т.д. Текстовые узлы отличаются от узлов-элементов. Но у них есть общие свойства и методы, потому что все классы DOM-узлов образуют единую иерархию.
Каждый DOM-узел принадлежит соответствующему встроенному классу.
Корнем иерархии является EventTarget, от него наследует Node и остальные DOM-узлы.
На рисунке ниже изображены основные классы:
Существуют следующие классы:
-
– это корневой «абстрактный» класс. Объекты этого класса никогда не создаются. Он служит основой, благодаря которой все DOM-узлы поддерживают так называемые «события», о которых мы поговорим позже. – также является «абстрактным» классом, и служит основой для DOM-узлов. Он обеспечивает базовую функциональность: parentNode , nextSibling , childNodes и т.д. (это геттеры). Объекты класса Node никогда не создаются. Но есть определённые классы узлов, которые наследуют от него: Text – для текстовых узлов, Element – для узлов-элементов и более экзотический Comment – для узлов-комментариев. – это базовый класс для DOM-элементов. Он обеспечивает навигацию на уровне элементов: nextElementSibling , children и методы поиска: getElementsByTagName , querySelector . Браузер поддерживает не только HTML, но также XML и SVG. Класс Element служит базой для следующих классов: SVGElement , XMLElement и HTMLElement . – является базовым классом для всех остальных HTML-элементов. От него наследуют конкретные элементы:
- …и т.д, каждому тегу соответствует свой класс, который предоставляет определённые свойства и методы.
- HTMLInputElement – этот класс предоставляет специфичные для элементов формы свойства,
- HTMLElement – предоставляет общие для HTML-элементов методы (и геттеры/сеттеры),
- Element – предоставляет типовые методы элемента,
- Node – предоставляет общие свойства DOM-узлов,
- EventTarget – обеспечивает поддержку событий (поговорим о них дальше),
- …и, наконец, он наследует от Object , поэтому доступны также методы «обычного объекта», такие как hasOwnProperty .
- console.log(elem) выводит элемент в виде DOM-дерева.
- console.dir(elem) выводит элемент в виде DOM-объекта, что удобно для анализа его свойств.
- elem.nodeType == 1 для узлов-элементов,
- elem.nodeType == 3 для текстовых узлов,
- elem.nodeType == 9 для объектов документа, можно посмотреть остальные значения.
- Свойство tagName есть только у элементов Element .
- Свойство nodeName определено для любых узлов Node :
- для элементов оно равно tagName .
- для остальных типов узлов (текст, комментарий и т.д.) оно содержит строку с типом узла.
Другими словами, свойство tagName есть только у узлов-элементов (поскольку они происходят от класса Element ), а nodeName может что-то сказать о других типах узлов.
Например, сравним tagName и nodeName на примере объекта document и узла-комментария:
Если мы имеем дело только с элементами, то можно использовать tagName или nodeName , нет разницы.
В браузере существуют два режима обработки документа: HTML и XML. HTML-режим обычно используется для веб-страниц. XML-режим включается, если браузер получает XML-документ с заголовком: Content-Type: application/xml+xhtml .
В HTML-режиме значения tagName/nodeName всегда записаны в верхнем регистре. Будет выведено BODY вне зависимости от того, как записан тег в HTML <body> или <BoDy> .
В XML-режиме регистр сохраняется «как есть». В настоящее время XML-режим применяется редко.
innerHTML: содержимое элемента
Свойство innerHTML позволяет получить HTML-содержимое элемента в виде строки.
Мы также можем изменять его. Это один из самых мощных способов менять содержимое на странице.
Пример ниже показывает содержимое document.body , а затем полностью заменяет его:
Мы можем попробовать вставить некорректный HTML, браузер исправит наши ошибки:
Если innerHTML вставляет в документ тег <script> – он становится частью HTML, но не запускается.
Будьте внимательны: «innerHTML+=» осуществляет перезаписьМы можем добавить HTML к элементу, используя elem.innerHTML+="ещё html" .
На практике этим следует пользоваться с большой осторожностью, так как фактически происходит не добавление, а перезапись.
Технически эти две строки делают одно и то же:
Другими словами, innerHTML+= делает следующее:
- Старое содержимое удаляется.
- На его место становится новое значение innerHTML (с добавленной строкой).
Так как содержимое «обнуляется» и переписывается заново, все изображения и другие ресурсы будут перезагружены.
В примере chatDiv выше строка chatDiv.innerHTML+="Как дела?" заново создаёт содержимое HTML и перезагружает smile.gif (надеемся, картинка закеширована). Если в chatDiv много текста и изображений, то эта перезагрузка будет очень заметна.
Есть и другие побочные эффекты. Например, если существующий текст выделен мышкой, то при переписывании innerHTML большинство браузеров снимут выделение. А если это поле ввода <input> с текстом, введённым пользователем, то текст будет удалён. И т.д.
К счастью, есть и другие способы добавить содержимое, не использующие innerHTML , которые мы изучим позже.
outerHTML: HTML элемента целиком
Свойство outerHTML содержит HTML элемента целиком. Это как innerHTML плюс сам элемент.
Посмотрим на пример:
Будьте осторожны: в отличие от innerHTML , запись в outerHTML не изменяет элемент. Вместо этого элемент заменяется целиком во внешнем контексте.
Да, звучит странно, и это действительно необычно, поэтому здесь мы и отмечаем это особо.
Какая-то магия, да?
В строке (*) мы заменили div на <p>Новый элемент</p> . Во внешнем документе мы видим новое содержимое вместо <div> . Но, как видно в строке ( ** ), старая переменная div осталась прежней!
Это потому, что использование outerHTML не изменяет DOM-элемент, а удаляет его из внешнего контекста и вставляет вместо него новый HTML-код.
То есть, при div.outerHTML=. произошло следующее:
- div был удалён из документа.
- Вместо него был вставлен другой HTML <p>Новый элемент</p> .
- В div осталось старое значение. Новый HTML не сохранён ни в какой переменной.
Здесь легко сделать ошибку: заменить div.outerHTML , а потом продолжить работать с div , как будто там новое содержимое. Но это не так. Подобное верно для innerHTML , но не для outerHTML .
Мы можем писать в elem.outerHTML , но надо иметь в виду, что это не меняет элемент, в который мы пишем. Вместо этого создаётся новый HTML на его месте. Мы можем получить ссылки на новые элементы, обратившись к DOM.
nodeValue/data: содержимое текстового узла
Свойство innerHTML есть только у узлов-элементов.
У других типов узлов, в частности, у текстовых, есть свои аналоги: свойства nodeValue и data . Эти свойства очень похожи при использовании, есть лишь небольшие различия в спецификации. Мы будем использовать data , потому что оно короче.
Прочитаем содержимое текстового узла и комментария:
Мы можем представить, для чего нам может понадобиться читать или изменять текстовый узел, но комментарии?
Иногда их используют для вставки информации и инструкций шаблонизатора в HTML, как в примере ниже:
…Затем JavaScript может прочитать это из свойства data и обработать инструкции.
textContent: просто текст
Свойство textContent предоставляет доступ к тексту внутри элемента за вычетом всех <тегов> .
Как мы видим, возвращается только текст, как если бы все <теги> были вырезаны, но текст в них остался.
На практике редко появляется необходимость читать текст таким образом.
Намного полезнее возможность записывать текст в textContent , т.к. позволяет писать текст «безопасным способом».
Представим, что у нас есть произвольная строка, введённая пользователем, и мы хотим показать её.
- С innerHTML вставка происходит «как HTML», со всеми HTML-тегами.
- С textContent вставка получается «как текст», все символы трактуются буквально.
Сравним два тега div:
- В первый <div> имя приходит «как HTML»: все теги стали именно тегами, поэтому мы видим имя, выделенное жирным шрифтом.
- Во второй <div> имя приходит «как текст», поэтому мы видим <b>Винни-пух!</b> .
В большинстве случаев мы рассчитываем получить от пользователя текст и хотим, чтобы он интерпретировался как текст. Мы не хотим, чтобы на сайте появлялся произвольный HTML-код. Присваивание через textContent – один из способов от этого защититься.
Свойство «hidden»
Атрибут и DOM-свойство «hidden» указывает на то, видим ли мы элемент или нет.
Мы можем использовать его в HTML или назначать при помощи JavaScript, как в примере ниже:
Технически, hidden работает так же, как style="display:none" . Но его применение проще.
Другие свойства
У DOM-элементов есть дополнительные свойства, в частности, зависящие от класса:
- value – значение для <input> , <select> и <textarea> ( HTMLInputElement , HTMLSelectElement …).
- href – адрес ссылки «href» для <a href=". "> ( HTMLAnchorElement ).
- id – значение атрибута «id» для всех элементов ( HTMLElement ).
- …и многие другие…
Большинство стандартных HTML-атрибутов имеют соответствующее DOM-свойство, и мы можем получить к нему доступ.
Если мы хотим узнать полный список поддерживаемых свойств для данного класса, можно найти их в спецификации. Например, класс HTMLInputElement описывается здесь: https://html.spec.whatwg.org/#htmlinputelement.
Если же нам нужно быстро что-либо узнать или нас интересует специфика определённого браузера – мы всегда можем вывести элемент в консоль, используя console.dir(elem) , и прочитать все свойства. Или исследовать «свойства DOM» во вкладке Elements браузерных инструментов разработчика.
Итого
Каждый DOM-узел принадлежит определённому классу. Классы формируют иерархию. Весь набор свойств и методов является результатом наследования.
Главные свойства DOM-узла:
nodeType Свойство nodeType позволяет узнать тип DOM-узла. Его значение – числовое: 1 для элементов, 3 для текстовых узлов, и т.д. Только для чтения. nodeName/tagName Для элементов это свойство возвращает название тега (записывается в верхнем регистре, за исключением XML-режима). Для узлов-неэлементов nodeName описывает, что это за узел. Только для чтения. innerHTML Внутреннее HTML-содержимое узла-элемента. Можно изменять. outerHTML Полный HTML узла-элемента. Запись в elem.outerHTML не меняет elem . Вместо этого она заменяет его во внешнем контексте. nodeValue/data Содержимое узла-неэлемента (текст, комментарий). Эти свойства практически одинаковые, обычно мы используем data . Можно изменять. textContent Текст внутри элемента: HTML за вычетом всех <тегов> . Запись в него помещает текст в элемент, при этом все специальные символы и теги интерпретируются как текст. Можно использовать для защиты от вставки произвольного HTML кода. hidden Когда значение установлено в true , делает то же самое, что и CSS display:none .
В зависимости от своего класса DOM-узлы имеют и другие свойства. Например у элементов <input> ( HTMLInputElement ) есть свойства value , type , у элементов <a> ( HTMLAnchorElement ) есть href и т.д. Большинство стандартных HTML-атрибутов имеют соответствующие свойства DOM.
Впрочем, HTML-атрибуты и свойства DOM не всегда одинаковы, мы увидим это в следующей главе.
-
– класс для тега <input> , – класс для тега <body> , – класс для тега <a> ,
Таким образом, полный набор свойств и методов данного узла собирается в результате наследования.
Рассмотрим DOM-объект для тега <input> . Он принадлежит классу HTMLInputElement.
Он получает свойства и методы из (в порядке наследования):
Для того, чтобы узнать имя класса DOM-узла, вспомним, что обычно у объекта есть свойство constructor . Оно ссылается на конструктор класса, и в свойстве constructor.name содержится его имя:
…Или мы можем просто привести его к строке :
Проверить наследование можно также при помощи instanceof :
Как видно, DOM-узлы – это обычные JavaScript объекты. Для наследования они используют классы, основанные на прототипах.
В этом легко убедиться, если вывести в консоли браузера любой элемент через console.dir(elem) . Или даже напрямую обратиться к методам, которые хранятся в HTMLElement.prototype , Element.prototype и т.д.
Большинство браузеров поддерживают в инструментах разработчика две команды: console.log и console.dir . Они выводят свои аргументы в консоль. Для JavaScript-объектов эти команды обычно выводят одно и то же.
Но для DOM-элементов они работают по-разному:
Попробуйте сами на document.body .
В спецификации для описания классов DOM используется не JavaScript, а специальный язык Interface description language (IDL), с которым достаточно легко разобраться.
В IDL все свойства представлены с указанием их типов. Например, DOMString , boolean и т.д.
Небольшой отрывок IDL с комментариями:
Свойство «nodeType»
Свойство nodeType предоставляет ещё один, «старомодный» способ узнать «тип» DOM-узла.
Его значением является цифра:
В современных скриптах, чтобы узнать тип узла, мы можем использовать метод instanceof и другие способы проверить класс, но иногда nodeType проще использовать. Мы не можем изменить значение nodeType , только прочитать его.
Тег: nodeName и tagName
Получив DOM-узел, мы можем узнать имя его тега из свойств nodeName и tagName :
Есть ли какая-то разница между tagName и nodeName ?
Да, она отражена в названиях свойств, но не очевидна.