27 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]
Свойство hidden
Как правило, видим или невидим узел, определяется через CSS, свойствами display или visibility.
В стандарте HTML5 предусмотрен специальный атрибут (он же свойство) для этого: hidden.
Его поддерживают все современные браузеры, кроме старых IE.
В примере ниже второй и третий <div> скрыты:
<!--+ run height="80" -->
<div>Текст</div>
<div hidden>С атрибутом hidden</div>
<div>Со свойством hidden</div>
<script>
var lastDiv = document.body.children[2];
lastDiv.hidden = true;
</script>
Технически, атрибут hidden работает так же, как style="display:none". Но его проще поставить через JavaScript (меньше букв), и могут быть преимущества для скринридеров и прочих нестандартных браузеров.
Для старых IE тоже можно сделать, чтобы свойство поддерживалось, мы ещё вернёмся к этому далее в учебнике.
Исследование элементов
У 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 и т.д. Мы рассмотрим их далее.