На этом занятии
мы с вами познакомимся с некоторыми часто используемыми свойствами DOM-узлов. Но для
начала посмотрим на иерархию классов объектов, из которых составляется DOM-дерево.
Понимать этот
рисунок можно так. Все элементы DOM содержат вот этот корневой класс EventTarget и класс Node, который
наследуется от него. Далее, начинается специализация классов: Text – для текстовых
элементов; Element – для тегов; Comment – для
комментариев. Для чего введена такая специализация? В частности, это позволяет
создавать специальные методы и свойства для различных типов DOM-узлов.
Например, текстовые элементы имеют свои специфические свойства, которых нет у
тегов-элементов или комментариев. А вот их общность наследования от классов Node и EventTarget предоставляет
некоторые общие методы, например, обработку реакций на события от
пользователей. Такого понимания этой иерархической структуры вполне достаточно
для восприятия дальнейшей информации.
Итак, базовые
классы EventTarget и Node являются
абстрактными, то есть, они служат только для построения других (дочерних)
классов, но сами по себе существовать не могут – только в составе других
классов. Остальные объекты могут быть созданы и быть узлом DOM-дерева.
Чтобы узнать имя
класса DOM-узла, обычно, у объекта есть свойство constructor. Оно ссылается на
конструктор класса, и в свойстве constructor.name содержится его имя:
console.log( document.body.constructor.name );
или же можно
проверить наследование с помощью оператора instanceof:
console.log( document.body instanceof HTMLBodyElement ); // true
console.log( document.body instanceof HTMLElement ); // true
console.log( document.body instanceof Element ); // true
console.log( document.body instanceof Text ); // false
Если мы хотим
отобразить в консоле структуру объекта, то вместо console.log следует
использовать console.dir:
console.dir(document.body);
а если записать
console.log(document.body);
то увидим
содержимое тега body.
В старых
стандартах языка JavaScript для определения типа узла DOM-дерева
использовалось свойство nodeType, которое содержало числовое значение:
-
elem.nodeType
== 1 для узлов-элементов;
-
elem.nodeType
== 3 для текстовых узлов;
-
…
-
elem.nodeType
== 9 для объектов документа.
console.log(document.body.nodeType);
Теперь
используется оператор instanceof, хотя в ряде случаев бывает удобно обратиться
к свойству nodeType.
Наконец, имя
узла можно узнать по свойствам nodeName и tagName. У всех без исключения DOM-узлов имеется
свойство nodeName, содержащее его название. Например, возьмем вот такой
документ:
<!DOCTYPE html>
<html>
<head>
<title>Уроки по JavaScript</title>
</head>
<body><!-- комментарий --><h1>Заголовок страницы</h1>
<script>
</script>
</body>
</html>
И посмотрим
свойство nodeName для комментария и тега:
let comm = document.body.firstChild;
console.log(comm.nodeName);
console.log(document.body.nodeName);
А вот если
отобразить свойство tagName, то получим следующее:
console.log(comm.tagName);
console.log(document.body.tagName);
Видите, у
комментария нет свойства tagName, оно есть только у тега-элемента
и имеет то же значение, что и nodeName. Это справедливо и для всех
остальных типов элементов: tagName – имя свойства тега-элемента; nodeName – имя свойства
любого узла DOM-дерева. В общем
случае мы всегда можем использовать nodeName для любых
узлов, чтобы узнать его имя.
innerHTML
У
элементов-тегов имеется свойство innerHTML, позволяющее читать и менять
содержимое любых тегов HTML-документа. Например, вот так
let h1 = document.body.firstChild.nextSibling;
console.log(h1.innerHTML);
можно вывести
содержимое заголовка h1 в консоль. А вот так изменить его:
h1.innerHTML = "Измененный заголовок";
или, даже так:
h1.innerHTML = "Измененный <u>заголовок</u>";
Смотрите, в
последнем примере мы добавили в заголовок не только текст, но и тег
форматирования <u>. И этот тег был применен при отображении
заголовка. Это важный момент: с помощью свойства innerHTML можно менять не
только текст, но и структуру HTML-документа с добавлением новых тегов.
Правда, здесь есть одно ограничение. Если мы вставляем тег скрипта <script>, то он
добавляется на страницу, но не запускается. Например, пусть имеется тег:
и, далее в скрипте:
para.innerHTML = `<script>
alert("hello");
<\/script>`;
Скрипт не запустится. Также не следует
полагать, что запись вида:
h1.innerHTML += " с добавленным текстом";
просто добавляет
строку к уже имеющемуся содержимому. В этом случае сформируется новая строка:
"Измененный
<u>заголовок</u> с добавленным текстом"
Затем, эта
строка скопируется в h1.innerHTML и браузер полностью обновит весь заголовок. То
есть, если вы, например, делаете чат и думаете таким образом добавлять в него
информацию с новыми сообщениями, то это приведет не только к добавлению, но и
обновлению содержимого всего чата. И это может быть нежелательным эффектом:
представьте, что все картинки и видео у пользователя начинают грузиться заново!
Наверное, это не то, что хотелось бы? Вот это следует учитывать при работе со
свойством innerHTML.
outerHTML
Следующее
свойство тега-элемента outerHTML работает также как и innerHTML, но содержит
информацию не только о содержимом тега, но и сам тег. С его помощью мы можем
изменять тег целиком, то есть, на его место записывать другой тег. Например:
h1.outerHTML = "<h2>Измененный <u>заголовок</u> из h1 в h2";
Теперь у нас
вместо заголовка h1 появляется заголовок h2 с новым
содержимым. И здесь есть одна тонкость: переменная h1 продолжает
содержать информацию о заголовке h1:
То есть, мы на
странице заменили этот заголовок, но в переменной прежняя информация продолжает
существовать. В частности, если вывести содержимое свойства outerHTML, то увидим
прежний заголовок:
console.log(h1.outerHTML);
Поэтому, как
только мы заменяем какой-либо элемент через свойство outerHTML, прежнюю
переменную следует либо обновить новой информацией, либо отбросить и в
дальнейшем не использовать.
nodeValue и data
Следующие два
свойства – это nodeValue и data. Как мы отмечали выше, свойство innerHTML
хранит информацию только о содержимом тегов-элементов. Доступ к данным
остальных типов узлов осуществляется через эти два свойства: nodeValue и data.
Они, в общем то, эквивалентны, поэтому на практике чаще используют data, так как оно
короче в записи. Например, отобразим содержимое комментария:
let comm = document.body.firstChild;
console.log(comm.data);
И в консоле
увидим слово «комментарий», которое записано внутри комментария. Причем, эти
свойства nodeValue и data отсутствуют у тегов-элементов. Если мы возьмем тег body и обратимся к
свойству data, то получим
значение undefined:
console.log(document.body.data);
textContent
Следующее
полезное свойство тегов – textContent, которое содержит чистый текст без тегов.
Например, отобразим текст содержимое тега body, получим:
console.log(document.body.textContent);
Как видите в
консоле у нас только текст, прописанный внутри этого элемента без каких-либо
тегов.
Где и когда
используется это свойство. Представьте, что вы создали страницу, где каждый
пользователь может оставить комментарий. Ушлые посетители вашего сайта
обязательно захотят попробовать свои недюжие хакерские способности, чтобы
исказить содержимое страницы или даже нарушить работу всего сайта. Как они это
будут делать? Да очевидно – прописывать теги в своих посланиях, в том числе и
теги <script> с надеждой
выполнить свой скрипт на вашей странице. Для защиты от таких нехитрых атак как
раз и можно использовать свойство textContent следующим образом. Предположим,
на нашей странице имеется раздел div с комментариями:
И мы просим
пользователя ввести какую-либо строчку:
let msg = prompt("Ваш комментарий:", "");
и, затем,
добавляем его в блок div:
Если теперь
пользователь вводит что-то вроде:
<script>Это мой вредоносный скрипт</script>
то в
комментариях появится ровно такая строка вместе с тегами и никакого вреда сайту
нанесено не будет. А вот, если бы мы использовали свойство innerHTML:
то теги бы были
прописаны в структуру документа:
что мы и видим
на нашей странице. Вот ключевое отличие между textContent и innerHTML.
hidden
Данный атрибут
позволяет скрывать элемент, то есть, не отображать его на экране браузера,
например:
<div id="comm">Сообщения от пользователей</div>
и далее:
let comm = document.getElementById("comm");
comm.hidden = true;
Или так:
setInterval(()=> comm.hidden = !comm.hidden, 500 );
тогда наш блок div будет то
пропадать, то появляться через каждые пол секунды. В целом, это свойство
подобно свойству style="display:none", но его удобнее использовать.
Конечно, узлы DOM-дерева имеют и
множество других свойств. Но, чтобы не перегружать занятие информацией, мы
будем с ними постепенно знакомиться по мере прохождения материала.