Свойства узлов: тип, тег и содержимое

Свойства узлов: тип, тег и содержимое

Теперь давайте более внимательно взглянем на 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-элементов. От него наследуют конкретные элементы:
      – класс для тега <input> , – класс для тега <body> , – класс для тега <a> ,
    • …и т.д, каждому тегу соответствует свой класс, который предоставляет определённые свойства и методы.

    Таким образом, полный набор свойств и методов данного узла собирается в результате наследования.

    Рассмотрим DOM-объект для тега <input> . Он принадлежит классу HTMLInputElement.

    Он получает свойства и методы из (в порядке наследования):

    • HTMLInputElement – этот класс предоставляет специфичные для элементов формы свойства,
    • HTMLElement – предоставляет общие для HTML-элементов методы (и геттеры/сеттеры),
    • Element – предоставляет типовые методы элемента,
    • Node – предоставляет общие свойства DOM-узлов,
    • EventTarget – обеспечивает поддержку событий (поговорим о них дальше),
    • …и, наконец, он наследует от Object , поэтому доступны также методы «обычного объекта», такие как hasOwnProperty .

    Для того, чтобы узнать имя класса DOM-узла, вспомним, что обычно у объекта есть свойство constructor . Оно ссылается на конструктор класса, и в свойстве constructor.name содержится его имя:

    …Или мы можем просто привести его к строке :

    Проверить наследование можно также при помощи instanceof :

    Как видно, DOM-узлы – это обычные JavaScript объекты. Для наследования они используют классы, основанные на прототипах.

    В этом легко убедиться, если вывести в консоли браузера любой элемент через console.dir(elem) . Или даже напрямую обратиться к методам, которые хранятся в HTMLElement.prototype , Element.prototype и т.д.

    Большинство браузеров поддерживают в инструментах разработчика две команды: console.log и console.dir . Они выводят свои аргументы в консоль. Для JavaScript-объектов эти команды обычно выводят одно и то же.

    Но для DOM-элементов они работают по-разному:

    • console.log(elem) выводит элемент в виде DOM-дерева.
    • console.dir(elem) выводит элемент в виде DOM-объекта, что удобно для анализа его свойств.

    Попробуйте сами на document.body .

    В спецификации для описания классов DOM используется не JavaScript, а специальный язык Interface description language (IDL), с которым достаточно легко разобраться.

    В IDL все свойства представлены с указанием их типов. Например, DOMString , boolean и т.д.

    Небольшой отрывок IDL с комментариями:

    Свойство «nodeType»

    Свойство nodeType предоставляет ещё один, «старомодный» способ узнать «тип» DOM-узла.

    Его значением является цифра:

    • elem.nodeType == 1 для узлов-элементов,
    • elem.nodeType == 3 для текстовых узлов,
    • elem.nodeType == 9 для объектов документа, можно посмотреть остальные значения.

    В современных скриптах, чтобы узнать тип узла, мы можем использовать метод instanceof и другие способы проверить класс, но иногда nodeType проще использовать. Мы не можем изменить значение nodeType , только прочитать его.

    Тег: nodeName и tagName

    Получив DOM-узел, мы можем узнать имя его тега из свойств nodeName и tagName :

    Есть ли какая-то разница между tagName и nodeName ?

    Да, она отражена в названиях свойств, но не очевидна.

    • Свойство 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+= делает следующее:

      1. Старое содержимое удаляется.
      2. На его место становится новое значение 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:

      1. В первый <div> имя приходит «как HTML»: все теги стали именно тегами, поэтому мы видим имя, выделенное жирным шрифтом.
      2. Во второй <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 не всегда одинаковы, мы увидим это в следующей главе.