Свойства узлов: тип, тег и содержимое
Изучите иерархию классов DOM, свойства nodeType, nodeName, tagName и четыре свойства содержимого: textContent, innerHTML, outerHTML и nodeValue/data.
Каждый узел в дереве DOM является объектом JavaScript, и этот объект предоставляет свойства, которые сообщают вам какого типа узел, какой тег он представляет и какое содержимое хранит. Научившись уверенно читать эти свойства, вы сможете гораздо реже допускать ошибки при навигации по странице и её редактировании. В этой главе рассматриваются иерархия классов узлов, свойства типа и имени (nodeType, nodeName, tagName), а также четыре свойства содержимого, к которым вы будете обращаться ежедневно (textContent, innerHTML, outerHTML и nodeValue/data), — включая то, когда каждое из них безопасно использовать.
Если вы ещё не знакомы с тем, как DOM организован в виде дерева, сначала прочитайте Понимание узлов DOM.
Иерархия классов узлов
Объекты DOM не все имеют одинаковый набор свойств. Они выстроены в иерархию классов, где каждый класс добавляет возможности к родительскому:
Node— базовый класс. Каждый узел (элемент, текст, комментарий, документ) наследует от него. Он предоставляет общие свойства:nodeType,nodeName,nodeValue,parentNodeиchildNodes.Element— узел, являющийся тегом. ДобавляетtagName,innerHTML, атрибуты,childrenи методы запросов.HTMLElement— базовый класс для каждого конкретного HTML-элемента. Подклассы, такие какHTMLInputElement,HTMLAnchorElementиHTMLTableElement, добавляют свойства, специфичные для тега (например,input.value,a.href).TextиComment— листовые узлы, хранящие текст. Они наследуют отCharacterData, который добавляет свойствоdata.
Знание этой иерархии объясняет, почему у <div> есть innerHTML, а у текстового узла — нет: innerHTML относится к Element, а текстовый узел элементом не является. Вы можете узнать класс объекта с помощью Object.prototype.toString или имени конструктора:
const div = document.createElement('div');
console.log(div.constructor.name); // "HTMLDivElement"
console.log(div instanceof HTMLElement); // true
console.log(div instanceof Element); // true
console.log(div instanceof Node); // trueТипы и имена узлов
Типы узлов (nodeType)
Свойство nodeType — это целое число, идентифицирующее категорию узла. Оно доступно только для чтения и является наиболее надёжным способом ветвить логику при обходе DOM, поскольку текстовые узлы и узлы комментариев легко перепутать с элементами. Наиболее распространённые значения:
1— узел-элемент (Node.ELEMENT_NODE): HTML- или XML-тег, например<div>или<p>.2— узел-атрибут (Node.ATTRIBUTE_NODE): устаревший тип; современный код читает атрибуты черезgetAttribute()/setAttribute(), а не через обход узлов атрибутов.3— текстовый узел (Node.TEXT_NODE): текст между тегами, включая пробелы и переносы строк.8— узел-комментарий (Node.COMMENT_NODE): HTML-комментарий<!-- ... -->.9— узел-документ (Node.DOCUMENT_NODE): сам объектdocument, корень дерева.
Используйте именованные константы вместо чисел — они лучше читаются и никогда не изменятся:
const div = document.createElement('div');
div.textContent = 'Hello';
console.log(div.nodeType); // 1
console.log(div.nodeType === Node.ELEMENT_NODE); // true
const textNode = div.firstChild;
console.log(textNode.nodeType === Node.TEXT_NODE); // true<!-- snippet: html-result -->
<div id="example">Example node</div>
<script>
const node = document.getElementById("example");
node.innerHTML = 'Node type is: ' + node.nodeType;
</script>Имена узлов: nodeName и tagName
Оба свойства возвращают имя узла, но отличаются областью применения:
nodeNameсуществует у каждого узла. Для элемента возвращает имя тега; для текстового узла —"#text"; для комментария —"#comment"; для документа —"#document".tagNameсуществует только у элементов. Для не-элементного узла оно равноundefined.
В HTML-документах оба возвращают имя тега в верхнем регистре ("DIV", а не "div"), независимо от того, как вы написали тег:
const div = document.createElement('div');
console.log(div.nodeName); // "DIV"
console.log(div.tagName); // "DIV"
const textNode = document.createTextNode('hi');
console.log(textNode.nodeName); // "#text"
console.log(textNode.tagName); // undefinedПрактическое правило: используйте tagName, если вы уже знаете, что работаете с элементом; используйте nodeName, если можете иметь дело с узлом любого типа.
<!-- snippet: html-result -->
<div id="example"></div>
<script>
const element = document.getElementById('example');
element.innerHTML = 'nodeName: ' + element.nodeName;
</script>Содержимое узлов
Существуют четыре свойства для чтения и записи того, что находится «внутри» узла. Выбор правильного имеет значение для корректности, производительности и безопасности.
textContent — простой текст, безопасно
textContent получает или устанавливает текст узла и всех его потомков, игнорируя любую разметку. При чтении вы получаете объединённый текст с удалёнными тегами. При записи строка вставляется как обычный текст — все < или > экранируются, поэтому это безопасный вариант для отображения ненадёжных данных:
const div = document.createElement('div');
div.innerHTML = '<b>Hi</b> there';
console.log(div.textContent); // "Hi there" (tags stripped)
div.textContent = '<script>alert(1)<\/script>';
console.log(div.textContent); // "<script>alert(1)</script>" (inert text, not executed)<!-- snippet: html-result -->
<div id="example"></div>
<script>
const element = document.getElementById('example');
element.textContent = 'Updated text content';
</script>innerHTML — разметка внутри элемента
innerHTML получает или устанавливает HTML между открывающим и закрывающим тегами элемента. При установке строка разбирается как HTML и дочерние элементы перестраиваются:
const div = document.createElement('div');
div.innerHTML = '<p>Para <span>one</span></p>';
console.log(div.innerHTML); // "<p>Para <span>one</span></p>"
console.log(div.children.length); // 1 (the <p>)Предупреждение о безопасности: никогда не присваивайте ненадёжный ввод в
innerHTML. Поскольку браузер разбирает его как HTML, строка вроде<img src=x onerror=alert(1)>становится реальным элементом, выполняющим скрипт — это классическая уязвимость межсайтового скриптинга (XSS). Для пользовательских данных используйтеtextContentили выполняйте очистку перед вставкой.
outerHTML — элемент вместе с его тегом
outerHTML похож на innerHTML, но включает собственные открывающий и закрывающий теги элемента. Запись в него заменяет сам элемент в DOM, что имеет неожиданное следствие — ваша переменная по-прежнему указывает на старый, теперь отсоединённый узел:
const div = document.createElement('div');
div.id = 'box';
div.innerHTML = 'content';
console.log(div.outerHTML); // '<div id="box">content</div>'
// Setting outerHTML replaces the node; `div` is now stale.nodeValue / data — текст внутри текстовых и узлов-комментариев
Для текстовых узлов и узлов-комментариев (у которых нет innerHTML) используйте nodeValue или эквивалентное ему data для чтения и записи их содержимого:
const text = document.createTextNode('original');
console.log(text.nodeValue); // "original"
console.log(text.data); // "original"
text.data = 'changed';
console.log(text.nodeValue); // "changed"
// On an element node, nodeValue is null.
const div = document.createElement('div');
console.log(div.nodeValue); // nullЧто использовать?
| Свойство | Работает с | Читает | Парсит HTML при записи? | Безопасно для ненадёжных данных |
|---|---|---|---|---|
textContent | любым узлом | простой текст потомков | нет | да |
innerHTML | элементами | внутреннюю разметку | да | нет (риск XSS) |
outerHTML | элементами | элемент с его тегом | да (заменяет узел) | нет (риск XSS) |
nodeValue / data | текстом, комментарием | текст узла | нет | да |
Подробнее о вставке и замене содержимого читайте в Изменение документа.
Заключение
Чтение свойств узлов — основа надёжного DOM-скриптинга. Используйте nodeType (с константами Node.*), чтобы различать категории узлов; tagName/nodeName — для идентификации тегов; и выбирайте свойство содержимого осознанно: textContent для безопасного простого текста, innerHTML/outerHTML — когда намеренно нужно разобрать разметку, и nodeValue/data — для текстовых узлов и узлов-комментариев. По умолчанию используйте textContent для всего, что ввёл пользователь.