26 KiB
Свойства узлов: тип, тег и содержимое
В этой главе мы познакомимся с основными, самыми важными свойствами, которые отвечают за тип DOM-узла, тег и содержимое.
[cut]
Классы, иерархия DOM
Самое главное различие между DOM-узлами -- разные узлы являются объектами различных классов.
Поэтому, к примеру, у узла, соответствующего тегу <td>
-- одни свойства, у <form>
-- другие, у <a>
-- третьи.
Есть и кое-что общее, за счёт наследования.
Классы DOM образуют иерархию.
Основной объект в ней: Node, от которого наследуют остальные:

На рисунке выше изображены основные классы:
- Прямо от `Node` наследуют текстовые узлы `Text`, комментарии `Comment` и элементы `Element`.
- Элементы `Element` -- это ещё не HTML-элементы, а более общий тип, который используется в том числе в XML. От него наследует `SVGElement` для SVG-графики и, конечно, `HTMLElement`.
- От `HTMLElement` уже наследуют разнообразные узлы HTML:
- Для `` -- `HTMLInputElement`
- Для `` -- `HTMLBodyElement`
- Для `` -- `HTMLAnchorElement`... и так далее.
Узнать класс узла очень просто -- достаточно привести его к строке, к примеру, вывести:
//+ run
alert( document.body ); // [object HTMLBodyElement]
Детальное описание свойств и методов каждого DOM-класса дано в спецификации.
Например, The input element описывает класс, соответствующий <input>
, включая interface HTMLInputElement, который нас как раз и интересует.
Вот из него выдержка:
interface HTMLInputElement : HTMLElement {
attribute DOMString accept;
attribute DOMString alt;
attribute DOMString autocomplete;
attribute boolean autofocus;
...
attribute DOMString value;
...
void select();
...
}
При описании свойств и методов используется не JavaScript, а специальный язык IDL (Interface Definition Language), который достаточно легко понять "с ходу".
В частности, выше мы видим, что:
- `HTMLInputElement` наследует от `HTMLEmenet`.
- У всех ``-элементов есть свойства `accept`, `alt`, `autocomplete` и `value`, которые являются строками (`DOMString`), а также также свойство `autofocus` с логическим значением.
- Также есть метод `select()`, который значение не возвращает (`void`).
Далее в этом разделе мы поговорим о самых главных свойствах узлов DOM, которые используются наиболее часто.
Тип: nodeType
Тип узла содержится в его свойстве nodeType
.
Как правило, мы работаем всего с двумя типами узлов:
- Элемент.
- Текстовый узел.
На самом деле типов узлов гораздо больше. Строго говоря, их 12, и они описаны в спецификации с древнейших времён, см. DOM Уровень 1:
interface Node {
// NodeType
const unsigned short ELEMENT_NODE = 1;
const unsigned short ATTRIBUTE_NODE = 2;
const unsigned short TEXT_NODE = 3;
const unsigned short CDATA_SECTION_NODE = 4;
const unsigned short ENTITY_REFERENCE_NODE = 5;
const unsigned short ENTITY_NODE = 6;
const unsigned short PROCESSING_INSTRUCTION_NODE = 7;
const unsigned short COMMENT_NODE = 8;
const unsigned short DOCUMENT_NODE = 9;
const unsigned short DOCUMENT_TYPE_NODE = 10;
const unsigned short DOCUMENT_FRAGMENT_NODE = 11;
const unsigned short NOTATION_NODE = 12;
...
}
В частности, тип "Элемент" ELEMENT_NODE
имеет номер 1, а "Текст" TEXT_NODE
-- номер 3.
Например, выведем все узлы-потомки document.body
, являющиеся элементами:
<!--+ run -->
<body>
<div>Читатели:</div>
<ul>
<li>Вася</li>
<li>Петя</li>
</ul>
<!-- комментарий -->
<script>
var childNodes = document.body.childNodes;
for (var i=0; i<childNodes.length; i++) {
*!*
// отфильтровать не-элементы
if (childNodes[i].nodeType != 1) continue;
*/!*
alert(childNodes[i]);
}
</script>
</body>
Тип узла можно только читать, изменить его невозможно.
Тег: nodeName и tagName
Существует целых два свойства: nodeName
и tagName
, которые содержат название(тег) элемента узла.
Название HTML-тега всегда находится в верхнем регистре.
Например, для document.body
:
//+ run
alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY
[smart header="В XHTML nodeName
может быть не в верхнем регистре"]
У браузера есть два режима обработки документа: HTML и XML-режим. Обычно используется режим HTML.
XML-режим включается, когда браузер получает XML-документ через XMLHttpRequest
(технология AJAX) или при наличии заголовка Content-Type: application/xml+xhtml
.
В XML-режиме сохраняется регистр и nodeName
может выдать "body" или даже "bOdY" -- в точности как указано в документе. XML-режим используют очень редко.
[/smart]
Какая разница между tagName и nodeName ?
Разница отражена в названиях свойств, но неочевидна.
- Свойство `tagName` есть только у элементов `Element` (в IE8- также у комментариев, но это ошибка в браузере).
- Свойство `nodeName` определено для любых узлов `Node`, для элементов оно равно `tagName`, а для не-элементов обычно содержит строку с типом узла.
Таким образом, при помощи tagName
мы можем работать только с элементами, а nodeName
может что-то сказать и о других типах узлов.
Например, сравним tagName
и nodeName
на примере узла-комментария и объекта document
:
<!--+ run -->
<body><!-- комментарий -->
<script>
// для комментария
alert(document.body.firstChild.nodeName); // #comment
alert(document.body.firstChild.tagName); // undefined (в IE8- воскл. знак "!")
// для документа
alert(document.nodeName); // #document, т.к. корень DOM -- не элемент
alert(document.tagName); // undefined
</script>
</body>
При работе с элементами, как это обычно бывает, имеет смысл использовать свойство tagName
-- оно короче.
innerHTML: содержимое элемента
Свойство innerHTML
описано в спецификации HTML 5 -- embedded content.
Оно позволяет получить HTML-содержимое элемента в виде строки. В innerHTML
можно и читать и писать.
Пример выведет на экран все содержимое document.body
, а затем заменит его на другое:
<!--+ run -->
<body>
<p>Параграф</p>
<div>Div</div>
<script>
alert( document.body.innerHTML ); // читаем текущее содержимое
document.body.innerHTML = 'Новый BODY!'; // заменяем содержимое
</script>
</body>
Значение, возвращаемое innerHTML
-- всегда валидный HTML-код. При записи можно попробовать записать что угодно, но браузер исправит ошибки:
<!--+ run -->
<body>
<script>
document.body.innerHTML = '<b>тест'; // незакрытый тег
alert(document.body.innerHTML); // <b>тест</b> (исправлено)
</script>
</body>
Свойство innerHTML
-- одно из самых часто используемых.
Тонкости innerHTML
innerHTML
не так прост, как может показаться, и таит в себе некоторые тонкости, которые могут сбить с толку новичка, а иногда и опытного программиста.
Ознакомьтесь с ними. Даже если этих сложностей у вас пока нет, эта информация отложится где-то в голове и поможет, когда проблема появится.
[warn header="Для таблиц в IE9- -- innerHTML
только для чтения"]
В Internet Explorer версии 9 и ранее, innerHTML доступно только для чтения для элементов COL
, COLGROUP
, FRAMESET
, HEAD
, HTML
, STYLE
, TABLE
, TBODY
, TFOOT
, THEAD
, TITLE
, TR
.
В частности, в IE9- запрещена запись в innerHTML
для любых табличных элементов, кроме ячеек (TD/TH
).
[/warn]
[warn header="Добавление innerHTML+=
осуществляет перезапись"]
Синтаксически, можно добавить текст к innerHTML
через +=
:
chatDiv.innerHTML += "<div>Привет<img src='smile.gif'/> !</div>";
chatDiv.innerHTML += "Как дела?";
На практике этим следует пользоваться с большой осторожностью, так как фактически происходит не добавление, а перезапись:
- Удаляется старое содержание
- На его место становится новое значение `innerHTML`.
Так как новое значение записывается с нуля, то все изображения и другие ресурсы будут перезагружены. В примере выше вторая строчка перезагрузит smile.gif
, который был до неё. Если в chatDiv
много текста, то эта перезагрузка будет очень заметна.
Есть и другие побочные эффекты, например если существующий текст был выделен мышкой, то в большинстве браузеров это выделение пропадёт. Если в HTML был <input>
, в который посетитель что-то ввёл, то введённое значение пропадёт. И тому подобное.
К счастью, есть и другие способы добавить содержимое, не использующие innerHTML
.
[/warn]
[warn header="Скрипты не выполняются"]
Если в innerHTML
есть тег script
-- он не будет выполнен.
К примеру:
<!--+ run -->
<div id="my"></div>
<script>
var elem = document.getElementById('my');
elem.innerHTML = 'ТЕСТ<script>alert(1);</scr'+'ipt>';
</script>
В примере закрывающий тег </scr'+'ipt>
разбит на две строки, т.к. иначе браузер подумает, что это конец скрипта. Вставленный скрипт не выполнится.
Исключение -- IE9-, в нем вставляемый скрипт выполняются, если у него есть атрибут defer
. Но это нестандартная возможность, которой не следует пользоваться.
[/warn]
[warn header="IE8- обрезает style
и script
в начале innerHTML
"]
Если в начале innerHTML
находятся стили <style>
, то старый IE проигнорирует их. То есть, иными словами, они не применятся.
Смотрите также innerHTML на MSDN на эту тему. [/warn]
outerHTML: HTML элемента целиком
Свойство outerHTML
содержит HTML элемента целиком.
Пример чтения outerHTML
:
<!--+ run -->
<div>Привет <b>Мир</b></div>
<script>
var div = document.body.children[0];
alert(div.outerHTML); // <div>Привет <b>Мир</b></div>
</script>
Изменить outerHTML
элемента невозможно.
Здесь мы остановимся чуть подробнее. Дело в том, что технически свойство outerHTML
доступно на запись. Но при этом элемент не меняется, а заменяется на новый, который тут же создаётся из нового outerHTML
.
При этом переменная, в которой изначально был старый элемент, и в которой мы "перезаписали" outerHTML
, остаётся со старым элементом.
Это легко может привести к ошибкам, что видно на примере:
<!--+ run -->
<div>Привет, Мир!</div>
<script>
var div = document.body.children[0];
*!*
// заменяем div.outerHTML на <p>...</p>
*/!*
div.outerHTML = '<p>Новый элемент!</p>';
*!*
// ... но содержимое div.outerHTML осталось тем же, несмотря на "перезапись"
*/!*
alert(div.outerHTML); // <div>Привет, Мир!</div>
</script>
То, что произошло в примере выше -- так это замена div
в документе на новый узел <p>...</p>
. При этом переменная div
не получила этот новый узел! Она сохранила старое значение, чтение из неё это отлично показывает.
[warn header="Записал outerHTML
? Понимай последствия!"]
Иногда начинающие делают здесь ошибку: сначала заменяют div.outerHTML
, а потом продолжают работать с div
, как будто это изменившийся элемент. Такое возможно с innerHTML
, но не с outerHTML
.
Записать новый HTML в outerHTML
можно, но нужно понимать, что это никакое не изменение свойств узла, а создание нового.
Новосозданный узел не доступен сразу в переменной, хотя его, конечно, можно получить из DOM. [/warn]
nodeValue/data: содержимое текстового узла
Свойство innerHTML
есть только у узлов-элементов.
Содержимое других узлов, например, текстовых или комментариев, доступно на чтение и запись через свойство data
.
Его тоже можно читать и обновлять. Следующий пример демонстрирует это:
<!--+ run height="50" -->
<body>
Привет
<!-- Комментарий -->
<script>
for (var i=0; i<document.body.childNodes.length; i++) {
*!*
alert(document.body.childNodes[i].data);
*/!*
}
</script>
Пока
</body>
Если вы запустите этот пример, то увидите, как выводятся последовательно:
- `Привет` -- это содержимое первого узла (текстового).
- `Комментарий` -- это содержимое второго узла (комментария).
- `Пробелы` -- это содержимое небольшого пробельного узла после комментария до скрипта.
- `undefined` -- далее цикл дошёл до
<script>
, но это узел-элемент, у него нет `data`.
Вообще говоря, после <script>...</script>
и до закрытия </body>
в документе есть еще один текстовый узел. Однако, на момент работы скрипта браузер ещё не знает о нём, поэтому не выведет.
Свойство nodeValue
мы использовать не будем.
Оно работает так же, как data
, но на некоторых узлах, где data
нет, nodeValue
есть и имеет значение null
. Как-то использовать это тонкое отличие обычно нет причин.
Два свойства существуют по историческим причинам, мы будем использовать лишь data
, поскольку оно короче.
Текст: textContent
Свойство textContent
содержит только текст внутри элемента, за вычетом всех <тегов>
.
Оно поддерживается везде, кроме IE8-.
Например:
<!--+ run -->
<div>
<h1>Срочно в номер!</h1>
<p>Марсиане атакуют людей!</p>
</div>
<script>
var news = document.body.children[0];
// \n Срочно в номер!\n Марсиане атакуют людей!\n
alert( news.textContent );
</script>
Как видно из примера выше, возвращается в точности весь текст, включая переводы строк и пробелы, но без тегов.
Иными словами, elem.textContent
возвращает конкатенацию всех текстовых узлов внутри elem
.
Не сказать, чтобы эта информация была часто востребована.
Гораздо полезнее возможность записать текст в элемент, причём именно как текст!
В этом примере имя посетителя попадёт в первый div
как innerHTML
, а во второй -- как текст:
<!--+ run -->
<div></div>
<div></div>
<script>
var name = prompt("Введите имя?", "<b>Винни-пух</b>");
document.body.children[0].innerHTML = name;
document.body.children[1].textContent = name;
</script>
При запуске примера мы увидим, что в первый DIV
текст от посетителя вставился в именно как HTML, то есть теги стали именно тегами, а во второй -- как обычный текст.
Вряд ли мы действительно хотим, чтобы посетители вставляли в наш сайт произвольный HTML-код. Присваивание через textContent
-- один из способов от этого защититься.
[warn header="Нестандартное свойство innerText
"]
Всеми браузерами, кроме Firefox, поддерживается нестандартное свойство innerText.
У него, в некотором роде, преимущество перед textContent
в том, что оно по названию напоминает innerHTML
, его проще запомнить.
Однако, свойство innerText
не следует использовать, так как оно не стандартное и не будет стандартным.
Это свойство возвращает текст не в том виде, в котором он в DOM, а в том, в котором он виден -- как если бы мы выбрали содержимое элемента мышкой и скопировали его. В частности, если элемент невидим, то его текст возвращён не будет. Это довольно странная особенность существует по историческим причинам и скорее мешает, чем помогает.
Впрочем, при записи значения innerText
работает так же, как и textContent
.
[/warn]
Исследование элементов
У DOM-узлов есть и другие свойства, зависящие от типа, например:
- `value` -- значение для `INPUT`, `SELECT` или `TEXTAREA`
- `id` -- идентификатор
- `href` -- адрес ссылки
- ...многие другие...
Например:
<!--+ run height="80" -->
<input type="text" value="значение">
<script>
var input = document.body.children[0];
alert(input.type); // "text"
alert(input.id); // "input"
alert(input.value); // значение
</script>
Как узнать, какие свойства есть у данного типа элементов?
Это просто. Нужно либо посмотреть список элементов HTML5 и найти в нём интересующий вас элемент и прочитать секцию с interface
.
Если же недосуг или интересуют особенности конкретного браузера -- элемент всегда можно вывести в консоль вызовом console.dir(элемент)
.
Метод console.dir
выводит аргумент не в "красивом" виде, а как объект, который можно развернуть и исследовать.
Например:
//+ run
// в консоли можно будет увидеть все свойства DOM-объекта document
console.dir(document);
Итого
Основные свойства DOM-узлов:
- `nodeType`
- Тип узла. Самые популярные типы: `"1"` - для элементов и `"3"` - для текстовых узлов. Только для чтения.
- `nodeName/tagName`
- Название тега заглавными буквами. `nodeName` имеет специальные значения для узлов-неэлементов. Только для чтения.
- `innerHTML`
- Внутреннее содержимое узла-элемента в виде HTML. Можно изменять.
- `outerHTML`
- Полный HTML узла-элемента. При записи в `elem.outerHTML` переменная `elem` сохраняет старый узел.
- `nodeValue/data`
- Содержимое текстового узла или комментария. Свойство `nodeValue` также определено и для других типов узлов. Можно изменять.
Узлы DOM также имеют другие свойства, в зависимости от тега. Например, у INPUT
есть свойства value
и checked
, а у A
есть href
и т.д. Мы рассмотрим их далее.