Пропустить навигацию

Лекция 7. DOM – Document Object Model

Окружение: DOM, BOM и JS

Браузер дает доступ к иерархии объектов, которые мы можем использовать для разработки. 

На рисунке схематически отображена структура основных браузерных объектов.

На вершине стоит window, который еще называют глобальным объектом.

Все остальные объекты делятся на 3 группы – DOM, BOM и JavaScript.

Объектная модель документа (DOM)

Объектная модель документа (DOM) — объект document,через который осуществляется доступ к содержимому страницы.

Объектная модель браузера (BOM)

Объектная модель браузера (BOM) — объекты, методы и свойства для работы с браузером. BOM — это объекты для работы с чем угодно, кроме документа.

Доступ к фреймам, запросы к серверу, функции alert/confirm/prompt — все это BOM.

Объекты и функции JavaScript

Javascript — связующий все это язык, его объекты, свойства и функции.

Глобальный объект window имеет две роли:

  1. Это окно браузера. У него есть методы window.focus(),window.open() и другие.
  2. Это глобальный объект JavaScript.

Вот почему он на рисунке представлен зеленым и красным цветом.

DOM в примерах.

Основным инструментом работы и динамических изменений на странице является DOM (Document Object Model) - объектная модель, используемая для XML/HTML-документов.

В DOM страницы представляются в виде иерархического дерева элементов. Каждый листок этого дерева называется узлом и непосредственно связан с каким-то элементом страницы. Узлы, расположенные снизу называются дочерними по отношению к вышестоящим.

Каждый HTML-тег образует отдельный узел, каждый фрагмент текста - текстовый элемент, и т.п.

Простейший DOM

Построим, для начала, дерево DOM для следующего документа.

<html>
  <head>
    <title>Заголовок</title>
  </head>
  <body>
     Прекрасный документ
  </body>
</html>

 Самый внешний тег - <html>, поэтому дерево начинает расти от него.

Внутри <html> находятся два узла: <head> и <body> - они становятся дочерними узлами для <html>.

 

Теги образуют узлы-элементы (element node). Текст представлен текстовыми узлами (text node). И то и другое - равноправные узлы дерева DOM.

Рассмотрим теперь более жизненную страничку 

 <html>
    <head>
        <title>
            Mobile OS
        </title>
    </head>
    <body>
          Some mobile OS.
        <ol>
           <li>
               Android
           </li>
           <li>
               iOS
           </li>
           <li>
               Windows Phone
           </li>
        </ol>
    </body>
  </html>

 

Корневым элементом иерархии является html. У него есть два потомка. Первый - head, второй - body. И так далее, каждый вложенный тег является потомком тега выше:

 

На этом рисунке синим цветом обозначены элементы-узлы, черным - текстовые элементы.

Дерево образовано за счет синих элементов-узлов - тегов HTML.

А вот так выглядит дерево, если изобразить его прямо на HTML-страничке:

Пример с атрибутами

Рассмотрим чуть более сложный документ.

<html>
    <head>
        <title>Документ</title>
    </head>
    <body>
        <div id="dataKeeper">Data</div>
        <ul>
            <li style="background-color:red">Осторожно</li>
            <li class="info">Информация</li>
        </ul>
        <div id="footer">Made in Russia &copy;</div>
    </body>
</html>

 Верхний тег - html, у него дети head и body, и так далее. Получается дерево тегов:

Атрибуты

В этом примере у узлов есть атрибуты: style, class, id. Вообще говоря, атрибуты тоже считаются узлами в DOM-модели, родителем которых является элемент DOM, у которого они указаны.

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

Возможности, которые дает DOM

Зачем, нужна иерархическая модель DOM?

Каждый DOM-элемент является объектом и предоставляет свойства для манипуляции своим содержимым, для доступа к родителям и потомкам.

Для манипуляций с DOM используется объект document.

Используя document, можно получать нужный элемент дерева и менять его содержание.

Например, этот код получает первый элемент с тэгом ol, последовательно удаляет два элемента списка и затем добавляет их в обратном порядке:

var ol = document.getElementsByTagName('ol')[0]
var android = ol.removeChild(ol.firstChild)
var ios = ol.removeChild(ol.firstChild)
var windows = ol.removeChild(ol.firstChild)
ol.appendChild(windows)
ol.appendChild(ios)
ol.appendChild(android)

Разберем подробнее способы доступа и свойства элементов DOM.

Доступ к элементам

Любой доступ и изменения DOM берут свое начало от объекта document.

Начнем с вершины дерева.

document.documentElement - Самый верхний тег. В случае корректной HTML-страницы, это будет <html>.

document.body - Тег <body>, если есть в документе (обязан быть).

Следующий пример при нажатии на кнопку выдаст текстовое представление объектов document.documentElement и document.body. Сама строка зависит от браузера, хотя объекты везде одни и те же.

<html>
  <body>
     <script>
       function go() {
         alert(document.documentElement)
         alert(document.body)
      }
     </script>
     <input type="button" onclick="go()" value="Go"/>
  </body>
</html>

Типы DOM-элементов

У каждого элемента в DOM-модели есть тип. Его номер хранится в атрибуте elem.nodeType

Всего в DOM различают 12 типов элементов.

Обычно используется только один: Node.ELEMENT_NODE, номер которого равен 1. Элементам этого типа соответствуют HTML-теги.

Иногда полезен еще тип Node.TEXT_NODE, который равен 3. Это текстовые элементы.

Остальные типы в javascript программировании не используются.

Следующий пример при нажатии на кнопку выведет типы document.documentElement, а затем тип последнего потомка узла document.body. Им является текстовый узел.

<html>
  <body>
     <script>
       function go() {
         alert(document.documentElement.nodeType)
         alert(document.body.lastChild.nodeType)        
      }
     </script>
     <input type="button" onclick="go()" value="Go"/>
     Текст
  </body>
</html>

Например, вот так выглядел бы в браузере документ из примера выше, если каждый видимый элемент обвести рамкой с цифрой nodeType в правом верхнем углу.

<html>
    <head><title>...</title></head>
    <body>
        <div id="dataKeeper">Data</div>
        <ul>
            <li style="background-color:red">Осторожно</li>
            <li class="info">Информация</li>
        </ul>
        <div id="footer">Made in Russia &copy;</div>
    </body>
</html>

Дочерние элементы

С вершины дерева можно пойти дальше вниз. Для этого каждый DOM-узел содержит массив всех детей, отдельно - ссылки на первого и последнего ребенка и еще ряд полезных свойств.

Свойство 1: Все дочерние элементы, включая текстовые, находятся в массиве childNodes.

Свойство 2: Свойства firstChild и lastChild показывают на первый и последний дочерние элементы и равны null, если детей нет.

Свойство 3: Свойство parentNode указывает на родителя. Например, для <body> таким элементом является <html>.

Свойство 4: Свойства previousSibling и nextSibling указывают на левого и правого братьев узла.

В общем, если взять отдельно <body> с детьми из нормализованного DOM - такая картинка получается от тега <body>:

И такая - для ссылок наверх и между узлами:

  • Синяя линия - массив childNodes
  • Зеленые линии - свойства firstChild, lastChild.
  • Красная линия - свойство parentNode
  • Бордовая и лавандовая линии внизу - previousSibling, nextSibling

Этих свойств вполне хватает для удобного обращения к соседям.

Свойства элементов

У DOM-элементов есть масса свойств. Обычно используется максимум треть из них. Некоторые из них можно читать и устанавливать, другие - только читать.

Рассмотрим здесь еще некоторые (не все) свойства элементов, полезные при работе с DOM.

tagName

Атрибут есть у элементов-тегов и содержит имя тега в верхнем регистре, только для чтения.

Например,

alert(document.body.tagName) // => BODY

style

Это свойство управляет стилем. Оно аналогично установке стиля в CSS.

Например, можно установить element.style.width:

<input
  type="button"
  style="width: 300px"
  onclick="this.style.width = parseInt(this.style.width)-10+'px'"
  value="Укоротить на 10px"
/>

Обработчик события onclick обращается в этом примере к свойству this.style.width, т.к значением this в обработчике события является текущий элемент (т.е сама кнопка). Подробнее об этом - во введении в события.

className

Это свойство задает класс элемента. Оно полностью аналогично html-атрибуту "class". elem.className = 'newclass'

onclick, onkeypress, onfocus...

.. И другие свойства, начинающиеся на "on...", хранят функции-обработчики соответствующих событий. Например, можно присвоить обработчик события onclick.

Свойства и Атрибуты

У DOM-элементов в javascript есть свойства и атрибуты. И те и другие имеют имя и значение.
HTML-атрибуты и DOM-свойства обычно, но не всегда соответствуют друг другу, нужно понимать, что такое свойство и что такое атрибут, чтобы работать с ними правильно.

Свойства DOM-элементов

Узел DOM — это объект, поэтому, как и любой объект в JavaScript, он может содержать пользовательские свойства и методы. Поэтому любому узлу можно назначить свойство, используя обычный синтаксис.

var elem = document.getElementById('MyElement')
elem.mySuperProperty = 5

Значением свойства может быть любой объект.

elem.master = {
  name: petya
}
alert(elem.master.name)

Пользовательские DOM-свойства:

  • Могут иметь любое значение.
  • Работают за счет того, что DOM-узлы являются объектами JavaScript.
  • Названия свойств чувствительны к регистру.

DOM-Атрибуты

Элементам DOM, с другой стороны, соответствуют HTML-теги, у которых есть текстовые атрибуты.

В следующем примере элемент имеет атрибуты id, class и нестандартный (валидатор будет ругаться) атрибут alpha.

<div id="MyElement" class="big" alpha="omega"></div>

setAttribute(name, value) – Устанавливает значение атрибутаАтрибуты можно добавлять, удалять и изменять. Для этого есть специальные методы:

getAttribute(name) – Получить значение атрибута

hasAttribute(name) – Проверить, есть ли такой атрибут

removeAttribute(name) – Удалить атрибут

Эти методы работают со значением, которое находится в HTML.

Имя атрибута является регистронезависимым.

Название маленькими буквами

 

document.body.setAttribute('test', 123)

Большими буквами

document.body.getAttribute('TEST') // 123

Однако в Яваскрипт существует искусственное соответствие между свойством и атрибутом.

Синхронизация

А именно, браузер синхронизирует значения ряда свойств с атрибутами. Если меняется атрибут, то меняется и свойство с этим именем. И наоборот.

Например:

document.body.id = 5
alert(document.body.getAttribute('id'))

А теперь - наоборот

document.body.setAttribute('id', 'NewId')
alert(document.body.id)


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

Возможные значения

Название атрибута не зависит от регистра

Атрибуты с именами "abc" и "ABC" - один и тот же атрибут.

document.body.setAttribute('abc', 1)
document.body.setAttribute('ABC', 5)
alert(document.body.getAttribute('abc')) // => стало 5

Но свойства в разных регистрах - два разных свойства. 

document.body.abc = 1
document.body.ABC = 5
alert(document.body.abc) // => все еще 1


Атрибут можно установить любой, а свойство - нет. 
Но свойства в разных регистрах - два разных свойства.

Например, можно установить для тэга <body> атрибут tagName, но соответствующее свойство - только для чтения, поэтому оно не изменится:

document.body.setAttribute('tagName',1)
document.body.getAttribute('tagName') // 1
document.body.tagName  // "BODY"

Вообще говоря, браузер не гарантирует синхронизацию атрибута и свойства.

Итого:

  • Атрибуты — это то, что написано в HTML.
  • Свойство — это то, что находится в свойстве DOM-объекта.

Таблица сравнений для атрибутов и свойств:

Свойства

Атрибуты

Любое значение

Строка

Названия регистрозависимы

Не чувствительны к регистру

Не видны в innerHTML

Видны в innerHTML

Поиск элементов в DOM

Стандарт DOM предусматривает несколько средств поиска элемента. Это методы getElementById, getElementsByTagName и getElementsByName.

Поиск по id

Самый удобный способ найти элемент в DOM - это получить его по id. Для этого используется вызов document.getElementById(id)

Например, следующий код изменит цвет текста на голубой в div'е c id="dataKeeper": 

document.getElementById('dataKeeper').style.color = 'blue'

Поиск по тегу

Следующий способ - это получить все элементы с определенным тегом, и среди них искать нужный. Для этого служит document.getElementsByTagName(tag). Она возвращает массив из элементов, имеющих такой тег.

Например, можно получить второй элемент(нумерация в массиве идет с нуля) с тэгом li:

 

document.getElementsByTagName('LI')[1]

Что интересно, getElementsByTagName можно вызывать не только для document, но и вообще для любого элемента, у которого есть тег (не текстового). 

При этом будут найдены только те объекты, которые находятся под этим элементом.

Например, следующий вызов получает список элементов LI, находящихся внутри первого тега div:

document.getElementsByTagName('DIV')[0].getElementsByTagName('LI')

Получить всех потомков

Теперь перейдем к следующему методу getElementsByTagName, он позволяет найти сразу несколько элементов по имени тэга, например, все картинки, все абзацы (тэг <P>), и так далее  

  <input type="submit" value="Нажми на меня" OnClick="ImageClick()">
    <p>Проба</p>
    <p>Проба</p>
    <p>Проба</p>
    <p>Проба</p>
    <SCRIPT LANGUAGE="JavaScript" TYPE="text/javascript">
        function ImageClick() {
            arr=document.getElementsByTagName("P")
            for(i=0; i<=3; i++) arr[i].innerHTML="Абзац "+i;
        }

Вызов elem.getElementsByTagName('*') вернет список из всех детей узла elem в порядке их обхода.

Например, на таком DOM:

<div id="d1">
  <ol id="ol1">
    <li id="li1">1</li>
    <li id="li2">2</li>
  </ol>
</div>

Такой код:

var div = document.getElementById('d1')
var elems = div.getElementsByTagName('*')
for(var i=0; i<elems.length; i++) alert(elems[i].id)

Выведет последовательность: ol1, li1, li2.

 

Поиск по name:

getElementsByName

Метод document.getElementsByName(name) возвращает все элементы, у которых имя (атрибут name) равно данному.

Он работает только с теми элементами, для которых в спецификации явно предусмотрен атрибут name: это form, input, a, select, textarea и ряд других, более редких.

Метод document.getElementsByName не будет работать с остальными элементами типа div,p и т.п.

Создание и добавление элементов

метод document.createElement(тип) - Создает новый элемент с указанным тегом:

var newDiv = document.createElement('div')

Тут же можно и проставить свойства и содержание созданному элементу.

var newDiv = document.createElement('div')
newDiv.className = 'my-class'
newDiv.id = 'my-id'
newDiv.style.backgroundColor = 'red'
newDiv.innerHTML = 'Привет, мир!'

document.createTextNode(text) - Создает новый текстовый узел с данным текстом:

var textElem = document.createTextNode('Текстовый элемент');

Добавление в DOM

Добавить новый элемент к детям существующего элемента можно методом appendChild, который в DOM есть у любого тега.

Код из следующего примера добавляет новые элементы к списку:

<ul id="list">
 <li>Первый элемент</li>
</ul>

Список:

  • Первый элемент

Элемент-список UL

var list = document.getElementById('list')

Новый элемент

var li = document.createElement('LI')
li.innerHTML = 'Новый элемент списка'


Добавление в конец

list.appendChild(li)

Метод appendChild всегда добавляет элемент последним в список детей.

Добавление в конкретное место

Новый элемент можно добавить не в конец детей, а перед нужным элементом.

Для этого используется метод insertBefore родительского элемента.

Он работает так же, как и appendChild, но принимает вторым параметром элемент, перед которым нужно вставлять. parentElem.insertBefore(newElem, target)

 Например, в том же списке добавим элементы перед первым li.

<ul id="list2">
<li>Первый элемент</li>
</ul>

Первый элемент

Родительский элемент UL

var list = document.getElementById('list2')


Элемент для вставки перед ним (первый LI)

var firstLi = list.getElementsByTagName('LI')[0]

Новый элемент

var newListElem = document.createElement('LI')
newListElem.innerHTML = 'Новый элемент списка'


Вставка

list.insertBefore(newListElem, firstLi)

Метод insertBefore позволяет вставлять элемент в любое место, кроме как в конец. А с этим справляется appendChild. Так что эти методы дополняют друг друга.

Метода insertAfter нет, но нужную функцию легко написать на основе комбинации insertBefore и appendChild.

Удаление узла DOM

Чтобы убрать узел из документа - достаточно вызвать метод removeChild из его родителя.

parentElem.removeChild(elem)

Удаляет elem из списка детей parentElem.

Этот метод возвращают удаленный узел, то есть elem. Его можно вставить в другое место DOM.

Метод document.write

Метод document.write — один из наиболее древних методов добавления текста к документу.

У него есть существенные ограничения, поэтому он используется редко, но по своей сути он совершенно уникален и иногда, хоть и редко, может быть полезен.

Как работает document.write

Метод document.write(str) работает, только пока HTML-страница находится в процессе загрузки. Он дописывает текст в текущее место HTML ещё до того, как браузер построит из него DOM.

Нет никаких ограничений на содержимое document.write.

Строка просто пишется в HTML-документ без проверки структуры тегов, как будто она всегда там была.

Также существует метод document.writeln(str) — не менее древний, который добавляет после str символ перевода строки "\n".

Во время загрузки браузер читает документ и тут же строит из него DOM, по мере получения информации достраивая новые и новые узлы, и тут же отображая их. Этот процесс идет непрерывным потоком. Вы наверняка видели это, когда заходили на сайты в качестве посетителя — браузер зачастую отображает неполный документ, добавляя его новыми узлами по мере их получения.

Методы document.write и document.writeln пишут напрямую в текст документа, до того как браузер построит из него DOM, поэтому они могут записать в документ все, что угодно, любые стили и незакрытые теги.

Браузер учтёт их при построении DOM, точно так же, как учитывает очередную порцию HTML-текста.

Технически, вызвать document.write можно в любое время, однако, когда HTML загрузился, и браузер полностью построил DOM, документ становится «закрытым». Попытка дописать что-то в закрытый документ открывает его заново. При этом все текущее содержимое удаляется.

Если вы нажмёте на такую кнопку — содержимое уже загруженной страницы удалится:

<input type="button" onclick='document.write("Пустая страница!");' value="Запустить document.write('Пустая страница!')">

Из-за этой особенности document.write для загруженных документов не используют.

Метод document.write (или writeln) пишет текст прямо в HTML, как будто он там всегда был.

  • Этот метод редко используется, так как работает только из скриптов, выполняемых в процессе загрузки страницы. Запуск после загрузки приведёт к очистке документа.
  • Метод document.write очень быстр.

В отличие от установки innerHTML и DOM-методов, он не изменяет существующий документ, а работает на стадии текста, до того как DOM-структура сформирована.

BOM-объекты: navigator, screen, location, frames

navigator: платформа и браузер

Объект navigator содержит общую информацию о браузере и операционной системе.

Имеет два свойства:

  • navigator.userAgent — содержит информацию о браузере.
  • navigator.platform — содержит информацию о платформе, позволяет различать Windows/Linux/Mac и т.п..

Для вашего браузера значения: 

alert(navigator.userAgent);
alert(navigator.platform);

Объект screen содержит общую информацию об экране, включая его разрешение, цветность и т.п. Оно может быть полезно для определения, что код выполняется на мобильном устройстве с маленьким разрешением.screen

Текущее разрешение экрана посетителя по горизонтали/вертикали находится в alert(screen.width+"x"+screen.height);

Это свойство можно использовать для сбора статистической информации о посетителях.

JavaScript-код счетчиков считывает эту информацию и отправляет на сервер. Именно поэтому можно просматривать в статистике, сколько посетителей приходило с каким экраном.

location

Объект Location предоставляет информацию о текущем URL и позволяет JavaScript перенаправить посетителя на другой URL. Значением этого свойства является объект типа Location.

Методы и свойства Location

Самый главный метод — toString. Он возвращает полный URL.

Следующая кнопка выведет текущий адрес:

Код, которому нужно провести строковую операцию над Location, должен сначала привести объект к строке. Вот так будет ошибка:

// будет ошибка, т.к. location - не строка
alert( window.location.indexOf('://') );

… А так - правильно:

// привели к строке перед indexOf
alert( (window.location + '').indexOf('://') );

Все следующие свойства являются строками.
Колонка «Пример» содержит их значения для тестового URL:

(в коде набирать так: window.location.href)

Свойство

Описание

Пример

hash

часть URL, которая идет после символа решетки ‘#’, включая символ ‘#’

#test

host

хост и порт

www.google.com:80

href

весь URL

http://www.google.com:80/search?q=javascript#test

hostname

хост (без порта)

www.google.com

pathname

строка пути (относительно хоста)

/search

port

номер порта (если порт не указан, то пустая строка)

80

protocol

протокол

http: (двоеточие на конце)

search

часть адреса после символа "?", включая символ "?"

?q=javascript

Методы объекта Location

assign(url)

загрузить документ по данному url. Можно и просто приравнять window.location.href = url.

reload([forceget])

перезагрузить документ по текущему URL. Аргумент forceget - булево значение, если оно true, то документ перезагружается всегда с сервера, если false или не указано, то браузер может взять страницу из своего кэша.

replace(url) – заменить текущий документ на документ по указанному url.

toString() - Как обсуждалось выше, возвращает строковое представление URL.

При изменении любых свойств window.location, кроме hash, документ будет перезагружен, как если бы для модифицированного url был вызван метод window.location.assign().

Можно перенаправить и явным присвоением location, например:

// браузер загрузит страницу http://javascript.ru
window.location = "http://javascript.ru";

Еще пример. Он перезагрузит страницу с новыми параметрами после "?":

function refreshSearch(search) {
  window.location.search = search;
}

При вызове refreshSearch('My Data') на сервер отправится строка
с параметрами ?My%20Data.

frames

Коллекция, содержащая фреймы и ифреймы. Можно обращаться к ним как по номеру, так и по имени.

В frames содержатся window-объекты дочерних фреймов.

Следующий код переводит фрейм на новый URL:

<iframe name="example" src="http://example.com" width="200" height="100"></iframe>
<script>
window.frames.example.location = 'http://example.com';
</script>

Но свойства в разных регистрах - два разных свойства.