Окружение: DOM, BOM и JS
Браузер дает доступ к иерархии объектов, которые мы можем использовать для разработки.
На рисунке схематически отображена структура основных браузерных объектов.
На вершине стоит window
, который еще называют глобальным объектом.
Все остальные объекты делятся на 3 группы – DOM, BOM и JavaScript.
Объектная модель документа (DOM)
Объектная модель документа (DOM) — объект document,через который осуществляется доступ к содержимому страницы.
Объектная модель браузера (BOM)
Объектная модель браузера (BOM) — объекты, методы и свойства для работы с браузером. BOM — это объекты для работы с чем угодно, кроме документа.
Доступ к фреймам, запросы к серверу, функции alert/confirm/prompt
— все это BOM.
Объекты и функции JavaScript
Javascript — связующий все это язык, его объекты, свойства и функции.
Глобальный объект window
имеет две роли:
- Это окно браузера. У него есть методы
window.focus()
,window.open()
и другие. - Это глобальный объект 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 ©</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 ©</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.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>
Но свойства в разных регистрах - два разных свойства.