Время на прочтение
21 мин
Количество просмотров 109K
Содержание
- Введение
- Величины, типы и операторы
- Структура программ
- Функции
- Структуры данных: объекты и массивы
- Функции высшего порядка
- Тайная жизнь объектов
- Проект: электронная жизнь
- Поиск и обработка ошибок
- Регулярные выражения
- Модули
- Проект: язык программирования
- JavaScript и браузер
- Document Object Model
- Обработка событий
- Проект: игра-платформер
- Рисование на холсте
- HTTP
- Формы и поля форм
- Проект: Paint
- Node.js
- Проект: веб-сайт по обмену опытом
- Песочница для кода
Когда вы открываете веб-страницу в браузере, он получает исходный текст HTML и разбирает (парсит) его примерно так, как наш парсер из главы 11 разбирал программу. Браузер строит модель структуры документа и использует её, чтобы нарисовать страницу на экране.
Это представление документа и есть одна из игрушек, доступных в песочнице JavaScript. Вы можете читать её и изменять. Она изменяется в реальном времени – как только вы её подправляете, страница на экране обновляется, отражая изменения.
Структура документа
Можно представить HTML как набор вложенных коробок. Теги вроде <body>
и </body>
включают в себя другие теги, которые в свою очередь включают теги, или текст. Вот вам пример документа из предыдущей главы:
<!doctype html>
<html>
<head>
<title>Моя домашняя страничка</title>
</head>
<body>
<h1> Моя домашняя страничка </h1>
<p>Привет, я Марийн и это моя домашняя страничка.</p>
<p>А ещё я книжку написал! Читайте её
<a href="http://eloquentjavascript.net">здесь</a>.</p>
</body>
</html>
У этой страницы следующая структура:
Структура данных, использующаяся браузером для представления документа, отражает его форму. Для каждой коробки есть объект, с которым мы можем взаимодействовать и узнавать про него разные данные – какой тег он представляет, какие коробки и текст содержит. Это представление называется Document Object Model (объектная модель документа), или сокращённо DOM.
Мы можем получить доступ к этим объектам через глобальную переменную document. Её свойство documentElement ссылается на объект, представляющий тег . Он также предоставляет свойства head и body, в которых содержатся объекты для соответствующих элементов.
Деревья
Вспомните синтаксические деревья из главы 11. Их структура удивительно похожа на структуру документа браузера. Каждый узел может ссылаться на другие узлы, потомки, которые, в свою очередь, могут иметь своих собственных потомков. Эта структура – типичный пример вложенных структур, где элементы содержат подэлементы, похожие на них самих.
Мы зовём структуру данных деревом, когда она разветвляется, не имеет циклов (узел не может содержать сам себя явно или неявно), и имеет единственный ярко выраженный «корень». В случае DOM в качестве корня выступает document.documentElement.
Деревья часто встречаются в вычислительной науке. В дополнение к представлению рекурсивных структур вроде документа HTML или программ, они часто используются для работы с сортированными наборами данных, потому что элементы обычно проще найти или вставлять в отсортированное дерево, чем в отсортированный одномерный массив.
У типичного дерева есть разные узлы. У синтаксического дерева языка Egg были переменные, значения и приложения. У приложений всегда были дочерние ветви, а переменные и значения были «листьями», то есть узлами без дочерних ответвлений.
То же и у DOM. Узлы для обычных элементов, представляющих теги HTML, определяют структуру документа. У них могут быть дочерние узлы. Пример такого узла — document.body. Некоторые из этих дочерних узлов могут оказаться листьями – например, текст или комментарии (в HTML комментарии записываются между символами <!--
и -->
).
У каждого узлового объекта DOM есть свойство nodeType, содержащее цифровой код, определяющий тип узла. У обычных элементов он равен 1, что также определено в виде свойства-константы document.ELEMENT_NODE. У текстовых узлов, представляющих отрывки текста, он равен 3 (document.TEXT_NODE). У комментариев — 8 (document.COMMENT_NODE).
То есть, вот ещё один способ графически представить дерево документа:
Листья – текстовые узлы, а стрелки показывают взаимоотношения отец-ребёнок между узлами.
Стандарт
Использовать загадочные цифры для представления типа узла – это подход не в стиле JavaScript. Позже мы встретимся с другими частями интерфейса DOM, которые тоже кажутся чуждыми и нескладными. Причина в том, что DOM разрабатывался не только для JavaScript. Он пытается определить интерфейс, не зависящий от языка, который можно использовать и в других системах – не только в HTML, но и в XML, который представляет из себя формат данных общего назначения с синтаксисом, напоминающим HTML.
Получается неудобно. Хотя стандарты – и весьма полезная штука, в нашем случае преимущество независимости от языка не такое уж и полезное. Лучше иметь интерфейс, хорошо приспособленный к языку, который вы используете, чем интерфейс, который будет знаком при использовании разных языков.
Чтобы показать неудобную интеграцию с языком, рассмотрим свойство childNodes, которое есть у узлов DOM. В нём содержится объект, похожий на массив, со свойством length, и пронумерованные свойства для доступа к дочерним узлам. Но это – экземпляр типа NodeList, не настоящий массив, поэтому у него нет методов вроде slice или forEach.
Есть также проблемы, связанные с плохой продуманностью системы. К примеру, нельзя создать новый узел и сразу добавить к нему свойства или дочерние узлы. Сначала нужно его создать, затем добавить дочерние по одному, и в конце назначить свойства по одному, с использованием побочных эффектов. Код, плотно работающий с DOM, получается длинным, некрасивым и со множеством повторов.
Но эти проблемы не фатальные. JavaScript позволяет создавать абстракции. Легко написать вспомогательные функции, позволяющие выражать операции более понятно и коротко. Вообще, такого рода инструменты предоставляют много библиотек, направленных на программирование для браузера.
Обход дерева
Узлы DOM содержат много ссылок на соседние. Это показано на диаграмме:
Хотя тут показано только по одной ссылке каждого типа, у каждого узла есть свойство parentNode, указывающего на его родительский узел. Также у каждого узла-элемента (тип 1) есть свойство childNodes, указывающее на массивоподобный объект, содержащий его дочерние узлы.
В теории можно пройти в любую часть дерева, используя только эти ссылки. Но JavaScript предоставляет нам много дополнительных вспомогательных ссылок. Свойства firstChild и lastChild показывают на первый и последний дочерний элементы, или содержат null у тех узлов, у которых нет дочерних. previousSibling и nextSibling указывают на соседние узлы – узлы того же родителя, что и текущего узла, но находящиеся в списке сразу до или после текущей. У первого узла свойство previousSibling будет null, а у последнего nextSibling будет null.
При работе с такими вложенными структурами пригождаются рекурсивные функции. Следующая ищет в документе текстовые узлы, содержащие заданную строку, и возвращает true, когда находит:
function talksAbout(node, string) {
if (node.nodeType == document.ELEMENT_NODE) {
for (var i = 0; i < node.childNodes.length; i++) {
if (talksAbout(node.childNodes[i], string))
return true;
}
return false;
} else if (node.nodeType == document.TEXT_NODE) {
return node.nodeValue.indexOf(string) > -1;
}
}
console.log(talksAbout(document.body, "книг"));
// → true
Свойства текстового узла nodeValue содержит строчку текста.
Поиск элементов
Часто бывает полезным ориентироваться по этим ссылкам между родителями, детьми и родственными узлами и проходить по всему документу. Однако если нам нужен конкретный узел в документе, очень неудобно идти по нему, начиная с document.body и тупо перебирая жёстко заданный в коде путь. Поступая так, мы вносим в программу допущения о точной структуре документа – а её мы позже можем захотеть поменять. Другой усложняющий фактор – текстовые узлы создаются даже для пробелов между узлами. В документе из примера у тега body не три дочерних (h1 и два p), а целых семь: эти три плюс пробелы до, после и между ними.
Так что если нам нужен атрибут href из ссылки, мы не должны писать в программе что-то вроде: «второй ребёнок шестого ребёнка document.body». Лучше бы, если б мы могли сказать: «первая ссылка в документе». И так можно сделать:
var link = document.body.getElementsByTagName("a")[0];
console.log(link.href);
У всех узлов-элементов есть метод getElementsByTagName, собирающий все элементы с данным тэгом, которые происходят (прямые или не прямые потомки) от этого узла, и возвращает его в виде массивоподобного объекта.
Чтобы найти конкретный узел, можно задать ему атрибут id и использовать метод document.getElementById.
<p>Мой страус Гертруда:</p>
<p><img id="gertrude" src="img/ostrich.png"></p>
<script>
var ostrich = document.getElementById("gertrude");
console.log(ostrich.src);
</script>
Третий метод – getElementsByClassName, который, как и getElementsByTagName, ищет в содержимом узла-элемента и возвращает все элементы, содержащие в своём классе заданную строчку.
Меняем документ
Почти всё в структуре DOM можно менять. У узлов-элементов есть набор методов, которые используются для их изменения. Метод removeChild удаляет заданный дочерний узел. Для добавления узла можно использовать appendChild, который добавляет узел в конец списка, либо insertBefore, добавляющий узел, переданную первым аргументом, перед узлом, переданным вторым аргументом.
<p>Один</p>
<p>Два</p>
<p>Три</p>
<script>
var paragraphs = document.body.getElementsByTagName("p");
document.body.insertBefore(paragraphs[2], paragraphs[0]);
</script>
Узел может существовать в документе только в одном месте. Поэтому вставляя параграф «Три» перед параграфом «Один» мы фактически удаляем его из конца списка и вставляем в начало, и получаем «Три/Один/Два». Все операции по вставке узла приведут к его исчезновению с текущей позиции (если у него таковая была).
Метод replaceChild используется для замены одного дочернего узла другим. Он принимает два узла: новый, и тот, который надо заменить. Заменяемый узел должен быть дочерним узлом того элемента, чей метод мы вызываем. Как replaceChild, так и insertBefore в качестве первого аргумента ожидают получить новый узел.
Создание узлов
В следующем примере нам надо сделать скрипт, заменяющий все картинки (тег <img>
) в документе текстом, содержащимся в их атрибуте “alt”, который задаёт альтернативное текстовое представление картинки.
Для этого надо не только удалить картинки, но и добавить новые текстовые узлы им на замену. Для этого мы используем метод document.createTextNode.
<p>Это <img src="img/cat.png" alt="Кошка"> в
<img src="img/hat.png" alt="сапожках">.</p>
<p><button onclick="replaceImages()">Заменить</button></p>
<script>
function replaceImages() {
var images = document.body.getElementsByTagName("img");
for (var i = images.length - 1; i >= 0; i--) {
var image = images[i];
if (image.alt) {
var text = document.createTextNode(image.alt);
image.parentNode.replaceChild(text, image);
}
}
}
</script>
Получая строку, createTextNode даёт нам тип 3 узла DOM (текстовый), который мы можем вставить в документ, чтобы он был показан на экране.
Цикл по картинкам начинается в конце списка узлов. Это сделано потому, что список узлов, возвращаемый методом getElementsByTagName (или свойством childNodes) постоянно обновляется при изменениях документа. Если б мы начали с начала, удаление первой картинки привело бы к потере списком первого элемента, и во время второго прохода цикла, когда i равно 1, он бы остановился, потому что длина списка стала бы также равняться 1.
Если вам нужно работать с фиксированным списком узлов вместо «живого», можно преобразовать его в настоящий массив при помощи метода slice.
var arrayish = {0: "один", 1: "два", length: 2};
var real = Array.prototype.slice.call(arrayish, 0);
real.forEach(function(elt) { console.log(elt); });
// → один
// два
Для создания узлов-элементов (тип 1) можно использовать document.createElement. Метод принимает имя тега и возвращает новый пустой узел заданного типа. Следующий пример определяет инструмент elt, создающий узел-элемент и использующий остальные аргументы в качестве его детей. Эта функция потом используется для добавления дополнительной информации к цитате.
<blockquote id="quote">
Никакая книга не может быть закончена. Во время работы над ней мы узнаём достаточно для того, чтобы найти её незрелой сразу же после того, как мы отвлеклись от неё.
</blockquote>
<script>
function elt(type) {
var node = document.createElement(type);
for (var i = 1; i < arguments.length; i++) {
var child = arguments[i];
if (typeof child == "string")
child = document.createTextNode(child);
node.appendChild(child);
}
return node;
}
document.getElementById("quote").appendChild(
elt("footer", "—",
elt("strong", "Карл Поппер"),
", предисловие ко второму изданию ",
elt("em", "Открытое общество и его враги "),
", 1950"));
</script>
Атрибуты
К некоторым атрибутам элементов, типа href у ссылок, можно получить доступ через одноимённое свойство объекта. Это возможно для ограниченного числа часто используемых стандартных атрибутов.
Но HTML позволяет назначать узлам любые атрибуты. Это полезно, т.к. позволяет вам хранить дополнительную информацию в документе. Если вы придумаете свои названия атрибутов, их не будет среди свойств узла-элемента. Вместо этого вам надо будет использовать методы getAttribute и setAttribute для работы с ними.
<p data-classified="secret">Код запуска 00000000.</p>
<p data-classified="unclassified">У кошки четыре ноги.</p>
<script>
var paras = document.body.getElementsByTagName("p");
Array.prototype.forEach.call(paras, function(para) {
if (para.getAttribute("data-classified") == "secret")
para.parentNode.removeChild(para);
});
</script>
Рекомендую перед именами придуманных атрибутов ставить “data-“, чтобы быть уверенным, что они не конфликтуют с любыми другими. В качестве простого примера мы напишем подсветку синтаксиса, который ищет теги <pre> (“preformatted”, предварительно отформатированный – используется для кода и простого текста) с атрибутом data-language (язык) и довольно грубо пытается подсветить ключевые слова в языке.
function highlightCode(node, keywords) {
var text = node.textContent;
node.textContent = ""; // Очистим узел
var match, pos = 0;
while (match = keywords.exec(text)) {
var before = text.slice(pos, match.index);
node.appendChild(document.createTextNode(before));
var strong = document.createElement("strong");
strong.appendChild(document.createTextNode(match[0]));
node.appendChild(strong);
pos = keywords.lastIndex;
}
var after = text.slice(pos);
node.appendChild(document.createTextNode(after));
}
Функция highlightCode принимает узел <pre> и регулярку (с включённой настройкой global), совпадающую с ключевым словом языка программирования, которое содержит элемент.
Свойство textContent используется для получения всего текста узла, а затем устанавливается в пустую строку, что приводит к очищению узла. Мы в цикле проходим по всем вхождениям выражения keyword, добавляем между ними текст в виде простых текстовых узлов, а совпавший текст (ключевые слова) добавляем, заключая их в элементы <strong> (жирный шрифт).
Мы можем автоматически подсветить весь код страницы, перебирая в цикле все элементы <pre>, у которых есть атрибут data-language, и вызывая на каждом highlightCodeс правильной регуляркой.
var languages = {
javascript: /\b(function|return|var)\b/g /* … etc */
};
function highlightAllCode() {
var pres = document.body.getElementsByTagName("pre");
for (var i = 0; i < pres.length; i++) {
var pre = pres[i];
var lang = pre.getAttribute("data-language");
if (languages.hasOwnProperty(lang))
highlightCode(pre, languages[lang]);
}
}
Вот пример:
<p>А вот и она, функция идентификации:</p>
<pre data-language="javascript">
function id(x) { return x; }
</pre>
<script>highlightAllCode();</script>
Есть один часто используемый атрибут, class, имя которого является ключевым словом в JavaScript. По историческим причинам, когда старые реализации JavaScript не умели обращаться с именами свойств, совпадавшими с ключевыми словами, этот атрибут доступен через свойство под названием className. Вы также можете получить к нему доступ по его настоящему имени “class” через методы getAttribute и setAttribute.
Расположение элементов (layout)
Вы могли заметить, что разные типы элементов располагаются по-разному. Некоторые, типа параграфов <p> и заголовков <h1> растягиваются на всю ширину документа и появляются на отдельных строках. Такие элементы называют блочными. Другие, как ссылки <a> или акцентированный текст <strong> появляются на одной строчке с окружающим их текстом. Они называются встроенными (inline).
Для любого документа браузеры могут вычислить расположение элементов, раскладку, в которой у каждого элемента будет размер и положение на основе его типа и содержимого. Затем эта раскладка используется для создания внешнего вида документа.
Размер и положение элемента можно узнать через JavaScript. Свойства offsetWidth и offsetHeight выдают размер в пикселях, занимаемый элементом. Пиксель – основная единица измерений в браузерах, и обычно соответствует размеру минимальной точки экрана. Сходным образом, clientWidth и clientHeight дают размер внутренней части элемента, не считая бордюра (или, как говорят некоторые, поребрика).
<p style="border: 3px solid red">
Я в коробочке
</p>
<script>
var para = document.body.getElementsByTagName("p")[0];
console.log("clientHeight:", para.clientHeight);
console.log("offsetHeight:", para.offsetHeight);
</script>
Самый эффективный способ узнать точное расположение элемента на экране – метод getBoundingClientRect. Он возвращает объект со свойствами top, bottom, left, и right (сверху, снизу, слева и справа), которые содержат положение элемента относительно левого верхнего угла экрана в пикселях. Если вам надо получить эти данные относительно всего документа, вам надо прибавить текущую позицию прокрутки, которая содержится в глобальных переменных pageXOffset и pageYOffset.
Разбор документа – задача сложная. В целях быстродействия браузерные движки не перестраивают документ каждый раз после его изменения, а ждут так долго. как это возможно. Когда программа JavaScript, изменившая документ, заканчивает работу, браузеру надо будет просчитать новую раскладку страницы, чтобы вывести изменённый документ на экран. Когда программа запрашивает позицию или размер чего-либо, читая свойства типа offsetHeight или вызывая getBoundingClientRect, для предоставления корректной информации тоже необходимо рассчитывать раскладку.
Программа, которая периодически считывает раскладку DOM и изменяет DOM, заставляет браузер много раз пересчитывать раскладку, и в связи с этим будет работать медленно. В следующем примере есть две разные программы, которые строят линию из символов X шириной в 2000 пикс, и измеряют время работы.
<p><span id="one"></span></p>
<p><span id="two"></span></p>
<script>
function time(name, action) {
var start = Date.now(); // Текущее время в миллисекундах
action();
console.log(name, "заняло", Date.now() - start, "ms");
}
time("тупо", function() {
var target = document.getElementById("one");
while (target.offsetWidth < 2000)
target.appendChild(document.createTextNode("X"));
});
// → тупо заняло 32 ms
time("умно", function() {
var target = document.getElementById("two");
target.appendChild(document.createTextNode("XXXXX"));
var total = Math.ceil(2000 / (target.offsetWidth / 5));
for (var i = 5; i < total; i++)
target.appendChild(document.createTextNode("X"));
});
// → умно заняло 1 ms
</script>
Стили
Мы видели, что разные элементы HTML ведут себя по-разному. Некоторые показываются в виде блоков, другие встроенные. Некоторые добавляют визуальный стиль – например, <strong> делает жирным текст и <a> делает текст подчёркнутым и синим.
Внешний вид картинки в теге <img> или то, что ссылка в теге <a> при клике открывает новую страницу, связано с типом элемента. Но основные стили, связанные с элементом, вроде цвета текста или подчёркивания, могут быть нами изменены. Вот пример использования свойства style (стиль):
<p><a href=".">Обычная ссылка</a></p>
<p><a href="." style="color: green">Зелёная ссылка</a></p>
Атрибут «style» может содержать одно или несколько объявлений свойств (color), за которым следует двоеточие и значение. В случае нескольких объявлений они разделяются точкой с запятой: “color: red; border: none”.
Много всякого можно изменить при помощи стилей. Например, свойство display контролирует, показывается ли элемент в блочном или встроенном виде.
Текст показан <strong>встроенным</strong>,
<strong style="display: block">в виде блока</strong>, и
<strong style="display: none">вообще не виден</strong>.
Блочный элемент выводится отдельным блоком на собственной строке, а последний вообще не виден – display: none отключает показ элементов. Таким образом можно прятать элементы. Обычно это предпочтительно полному удалению их из документа, потому что их легче потом при необходимости снова показать.
Код JavaScript может напрямую действовать на стиль элемента через свойство узла style. В нём содержится объект, имеющий свойства для всех свойств стилей. Их значения – строки, в которые мы можем писать для смены какого-то аспекта стиля элемента.
<p id="para" style="color: purple">
Красотень
</p>
<script>
var para = document.getElementById("para");
console.log(para.style.color);
para.style.color = "magenta";
</script>
Некоторые имена свойств стилей содержат дефисы, например font-family. Так как с ними неудобно было бы работать в JavaScript (пришлось бы писать style[«font-family»]), названия свойств в объекте стилей пишутся без дефиса, а вместо этого в них появляются прописные буквы: style.fontFamily.
Каскадные стили
Система стилей в HTML называется CSS (Cascading Style Sheets, каскадные таблицы стилей). Таблица стилей – набор стилей в документе. Его можно писать внутри тега <style>
:
<style>
strong {
font-style: italic;
color: gray;
}
</style>
<p>Теперь <strong>текст тега strong</strong> наклонный и серый.</p>
«Каскадные» означает, что несколько правил комбинируются для получения окончательного стиля документа. В примере на стиль по умолчанию для <strong>
, который делает текст жирным, накладывается правило из тега <style>
, по которому добавляется font-style и цвет.
Когда значение свойства определяется несколькими правилами, приоритет остаётся у более поздних. Если бы стиль текста в <style>
включал правило font-weight: normal, конфликтующее со стилем по умолчанию, то текст был бы обычный, а не жирный. Стили, которые применяются к узлу через атрибут style, имеют наивысший приоритет.
В CSS возможно задавать не только название тегов. Правило для .abc применяется ко всем элементам, у которых указан класс “abc”. Правило для #xyz применяется к элементу с атрибутом id равным “xyz” (атрибуты id необходимо делать уникальными для документа).
.subtle {
color: gray;
font-size: 80%;
}
#header {
background: blue;
color: white;
}
/* Элементы p, у которых указаны классы a и b, а id задан как main */
p.a.b#main {
margin-bottom: 20px;
}
Приоритет самых поздних правил работает, когда у правил одинаковая детализация. Это мера того, насколько точно оно описывает подходящие элементы, определяемая числом и видом необходимых аспектов элементов. К примеру, правило для p.a более детально, чем правила для p или просто .a, и будет иметь приоритет над ними.
Запись p > a {…}
применима ко всем тегам <a>
, находящимся внутри тега <p>
и являющимся его прямыми потомками.
Подобным образом p a {…}
применимо также ко всем тегам <a>
внутри <p>
, при этом неважно, является ли <a>
прямым потомком или нет.
Селекторы запросов
В этой книге мы не будем часто использовать таблицы стилей. Понимание их работы критично для программирования в браузере, но подробное разъяснение всех их свойств заняло бы 2-3 книги. Главная причина знакомства с ними и с синтаксисом селекторов (записей, определяющих, к каким элементам относятся правила) – мы можем использовать тот же эффективный мини-язык для поиска элементов DOM.
Метод querySelectorAll, существующий и у объекта document, и у элементов-узлов, принимает строку селектора и возвращает массивоподобный объект, содержащий все элементы, подходящие под него.
<p>Люблю грозу в начале
<span class="animal">мая</span>,</p>
<p>Когда весенний, первый гром,</p>
<p>Как бы <span class="character">резвяся
<span class="animal">и играя</span></span>,</p>
<p>Грохочет в небе голубом.</p>
<script>
function count(selector) {
return document.querySelectorAll(selector).length;
}
console.log(count("p")); // Все элементы <p>
// → 4
console.log(count(".animal")); // Класс animal
// → 2
console.log(count("p .animal")); // Класс animal внутри <p>
// → 2
console.log(count("p > .animal")); // Прямой потомок <p>
// → 1
</script>
В отличие от методов вроде getElementsByTagName, возвращаемый querySelectorAll объект не интерактивный. Он не изменится, если вы измените документ.
Метод querySelector (без All) работает сходным образом. Он нужен, если вам необходим один конкретный элемент. Он вернёт только первое совпадение, или null, если совпадений нет.
Расположение и анимация
Свойство стилей position сильно влияет на расположение элементов. По умолчанию оно равно static, что означает, что элемент находится на своём обычном месте в документе. Когда оно равно relative, элемент всё ещё занимает место, но теперь свойства top и left можно использовать для сдвига относительно его обычного расположения. Когда оно равно absolute, элемент удаляется из нормального «потока» документа – то есть, он не занимает место и может накладываться на другие. Кроме того, его свойства left и top можно использовать для абсолютного позиционирования относительно левого верхнего угла ближайшего включающего его элемента, у которого position не равно static. А если такого элемента нет, тогда он позиционируется относительно документа.
Мы можем использовать это для создания анимации. Следующий документ показывает картинку с котом, которая двигается по эллипсу.
<p style="text-align: center">
<img src="img/cat.png" style="position: relative">
</p>
<script>
var cat = document.querySelector("img");
var angle = 0, lastTime = null;
function animate(time) {
if (lastTime != null)
angle += (time - lastTime) * 0.001;
lastTime = time;
cat.style.top = (Math.sin(angle) * 20) + "px";
cat.style.left = (Math.cos(angle) * 200) + "px";
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
</script>
Картинка отцентрирована на странице и ей задана position: relative. Мы постоянно обновляем свойства top и left картинки, чтобы она двигалась.
Скрипт использует requestAnimationFrame для вызова функции animate каждый раз, когда браузер готов перерисовывать экран. Функция animate сама опять вызывает requestAnimationFrame, чтобы запланировать следующее обновление. Когда окно браузера (или закладка) активна, это приведёт к обновлениям со скорость примерно 60 раз в секунду, что позволяет добиться хорошо выглядящей анимации.
Если бы мы просто обновляли DOM в цикле, страница бы зависла и ничего не было бы видно. Браузеры не обновляют страницу во время работы JavaScript, и не допускают в это время работы со страницей. Поэтому нам нужна requestAnimationFrame – она сообщает браузеру, что мы пока закончили, и он может заниматься своими браузерными вещами, например обновлять экран и отвечать на запросы пользователя.
Наша функция анимации передаётся текущее время через аргументы, которое оно сравнивает с предыдущим (переменная lastTime), чтобы движение кота было однородным, и анимация работала плавно. Если бы мы просто передвигали её на заданный промежуток на каждом шаге, движение бы запиналось если бы, например, другая задача загрузила бы компьютер.
Движение по кругу осуществляется с применением тригонометрических функций Math.cos и Math.sin. Я кратко опишу их для тех, кто с ними не знаком, так как они понадобятся нам в дальнейшем.
Math.cos и Math.sin полезны тогда, когда надо найти точки на окружности с центром в точке (0, 0) и радиусом в единицу. Обе функции интерпретируют свой аргумент как позицию на окружности, следуя против часовой стрелки, от нуля в самой правой точке, пока путь диной в 2π (около 6.28) не проведёт нас по кругу. Math.cos считает координату по оси x той точки, которая является нашей текущей позицией на окружности, а Math.sin выдаёт координату y. Позиции (или углы) больше, чем 2π или меньше чем 0, тоже допустимы – повороты повторяются так, что a+2π означает тот же самый угол, что и a.
Использование синуса и косинуса для вычисления координат
Анимация кота хранит счётчик angle для текущего угла поворота анимации, и увеличивает его пропорционально прошедшему времени каждый раз при вызове функции animation. Этот угол используется для подсчёта текущей позиции элемента image. Стиль top подсчитывается через Math.sin и умножается на 20 – это вертикальный радиус нашего эллипса. Стиль left считается через Math.cos и умножается на 200, так что ширина эллипса сильно больше высоты.
Стилям обычно требуются единицы измерения. В нашем случае приходится добавлять px к числу, чтобы объяснить браузеру, что мы считаем в пикселях (а не в сантиметрах, ems или других единицах). Это легко забыть. Использование чисел без единиц измерения приведёт к игнорированию стиля – если только число не равно 0, что не зависит от единиц измерения.
Итог
Программы JavaScript могут изучать и изменять текущий отображаемый браузером документ через структуру под названием DOM. Эта структура данных представляет модель документа браузера, а программа JavaScript может изменять её для изменения видимого документа. DOM организован в виде дерева, в котором элементы расположены иерархически в соответствии со структурой документа. У объектов элементов есть свойства типа parentNode и childNodes, которые используются для ориентирования на дереве.
Внешний вид документа можно изменять через стили, либо добавляя стили к узлам напрямую, либо определяя правила для каких-либо узлов. У стилей есть очень много свойств, таких, как color или display. JavaScript может влиять на стиль элемента напрямую через его свойство style.
Упражнения
Строим таблицу
Мы строили таблицы из простого текста в главе 6. HTML упрощает построение таблиц. Таблица в HTML строится при помощи следующих тегов:
<table>
<tr>
<th>name</th>
<th>height</th>
<th>country</th>
</tr>
<tr>
<td>Kilimanjaro</td>
<td>5895</td>
<td>Tanzania</td>
</tr>
</table>
Для каждой строки в теге <table>
содержится тег <tr>
. Внутри него мы можем размещать ячейки: либо ячейки заголовков <th>
, либо обычные ячейки <td>
.
Те же данные, что мы использовали в главе 6, снова доступны в переменной MOUNTAINS.
Напишите функцию buildTable, которая, принимая массив объектов с одинаковыми свойствами, строит структуру DOM, представляющую таблицу. У таблицы должна быть строка с заголовками, где имена свойств обёрнуты в элементы <th>
, и должно быть по одной строчке на объект из массива, где его свойства обёрнуты в элементы <td>
. Здесь пригодится функция Object.keys, возвращающая массив, содержащий имена свойств объекта.
Когда вы разберётесь с основами, выровняйте ячейки с числами по правому краю, изменив их свойство style.textAlign на «right».
<style>
/* Определяет стили для красивых таблиц */
table { border-collapse: collapse; }
td, th { border: 1px solid black; padding: 3px 8px; }
th { text-align: left; }
</style>
<script>
function buildTable(data) {
// Ваш код
}
document.body.appendChild(buildTable(MOUNTAINS));
</script>
Элементы по имени тегов
Метод getElementsByTagName возвращает все дочерние элементы с заданным именем тега. Сделайте свою версию этого метода в виде обычной функции, которая принимает узел и строчку (имя тега) и возвращает массив, содержащий все нисходящие узлы с заданным именем тега.
Чтобы выяснить имя тега элемента, используйте свойство tagName. Заметьте, что оно возвратит имя тега в верхнем регистре. Используйте методы строк toLowerCase или toUpperCase.
<h1>Заголовок с элементом <span>span</span> внутри.</h1>
<p>Параграф с <span>раз</span>, <span>два</span> элементами spans.</p>
<script>
function byTagName(node, tagName) {
// Ваш код
}
console.log(byTagName(document.body, "h1").length);
// → 1
console.log(byTagName(document.body, "span").length);
// → 3
var para = document.querySelector("p");
console.log(byTagName(para, "span").length);
// → 2
</script>
Шляпа кота
Расширьте анимацию кота, чтобы и кот и его шляпа <img src="img/hat.png">
летали по противоположным сторонам эллипса.
Или пусть шляпа летает вокруг кота. Или ещё что-нибудь интересное придумайте.
Чтобы упростить расположение множества объектов, неплохо будет переключиться на абсолютное позиционирование. Тогда top и left будут считаться относительно левого верхнего угла документа. Чтобы не использовать отрицательные координаты, вы можете добавить заданное число пикселей к значениям position.
<img src="img/cat.png" id="cat" style="position: absolute">
<img src="img/hat.png" id="hat" style="position: absolute">
<script>
var cat = document.querySelector("#cat");
var hat = document.querySelector("#hat");
// Your code here.
</script>
- Простейший DOM
- Пример посложнее
- Пример с атрибутами и DOCTYPE
- Нормализация в различных браузерах
- Возможности, которые дает DOM
-
Доступ к элементам
- document.documentElement
- document.body
-
Типы DOM-элементов
- Пример
- Дочерние элементы
-
Свойства элементов
-
tagName
-
style
-
innerHTML
-
className
-
onclick
,onkeypress,
onfocus
…
-
Основным инструментом работы и динамических изменений на странице является DOM (Document Object Model) — объектная модель, используемая для XML/HTML-документов.
Согласно DOM-модели, документ является иерархией.
Каждый HTML-тег образует отдельный элемент-узел, каждый фрагмент текста — текстовый элемент, и т.п.
Проще говоря, DOM — это представление документа в виде дерева тегов. Это дерево образуется за счет вложенной структуры тегов плюс текстовые фрагменты страницы, каждый из которых образует отдельный узел.
Простейший DOM
Построим, для начала, дерево DOM для следующего документа.
<html> <head> <title>Заголовок</title> </head> <body> Прекрасный документ </body> </html>
Самый внешний тег — <html>
, поэтому дерево начинает расти от него.
Внутри <html>
находятся два узла: <head>
и <body>
— они становятся дочерними узлами для <html>
.
Теги образуют узлы-элементы (element node). Текст представлен текстовыми узлами (text node). И то и другое — равноправные узлы дерева DOM.
Пример посложнее
Рассмотрим теперь более жизненную страничку:
<html> <head> <title> О лосях </title> </head> <body> Правда о лосях. <ol> <li> Лось - животное хитрое </li> <li> .. И коварное </li> </ol> </body> </html>
Корневым элементом иерархии является html
. У него есть два потомка. Первый — head
, второй — body
. И так далее, каждый вложенный тег является потомком тега выше:
На этом рисунке синим цветом обозначены элементы-узлы, черным — текстовые элементы.
Дерево образовано за счет синих элементов-узлов — тегов HTML.
А вот так выглядит дерево, если изобразить его прямо на HTML-страничке:
Кстати, дерево на этом рисунке не учитывает текст, состоящий из одних пробельных символов. Например, такой текстовый узел должен идти сразу после <ol>
. DOM, не содержащий таких «пустых» узлов, называют «нормализованным».
Пример с атрибутами и DOCTYPE
Рассмотрим чуть более сложный документ.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <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-узла, которые, как мы увидим в дальнейшем, можно устанавливать и менять по желанию программиста.
DOCTYPE
Вообще-то это секрет, но DOCTYPE тоже является DOM-узлом, и находится в дереве DOM слева от HTML (на рисунке этот факт скрыт).
P.S. Насчет секрета — конечно, шутка, но об этом и правда далеко не все знают. Сложно придумать, где такое знание может пригодиться…
Нормализация в различных браузерах
При разборе HTML Internet Explorer сразу создает нормализованный DOM, в котором не создаются узлы из пустого текста.
Firefox — другого мнения, он создает DOM-элемент из каждого текстового фрагмента.
Поэтому в Firefox дерево этого документа выглядит так:
На рисунке для краткости текстовые узлы обозначены просто решеткой. У body
вместо 3 появилось 7 детей.
Opera тоже имеет чем похвастаться. Она может добавить лишний пустой элемент «просто от себя».
Чтобы это увидеть — откройте документ по этой ссылке. Он выдает число дочерних узлов document.body
, включая текстовые узлы.
У меня получается 3 для IE, 7 для Firefox и 8 (!?) для Opera.
На практике эта несовместимость не создает больших проблем, но нужно о ней помнить. Например, разница может проявить себя в случае перебора узлов дерева.
Возможности, которые дает DOM
Зачем, кроме красивых рисунков, нужна иерархическая модель DOM?
Очень просто:
Каждый DOM-элемент является объектом и предоставляет свойства для манипуляции своим содержимым, для доступа к родителям и потомкам.
Для манипуляций с DOM используется объект document
.
Используя document
, можно получать нужный элемент дерева и менять его содержание.
Например, этот код получает первый элемент с тэгом ol
, последовательно удаляет два элемента списка и затем добавляет их в обратном порядке:
var ol = document.getElementsByTagName('ol')[0] var hiter = ol.removeChild(ol.firstChild) var kovaren = ol.removeChild(ol.firstChild) ol.appendChild(kovaren) ol.appendChild(hiter)
Для примера работы такого скрипта — кликните на тексте на лосиной cтраничке
document.write
В старых руководствах и скриптах можно встретить модификацию HTML-кода страницы напрямую вызовом document.write
.
В современных скриптах этот метод почти не используется, случаи его правильного применения можно пересчитать по пальцам.
Избегайте document.write.. Кроме случаев, когда вы действительно знаете, что делаете (а зачем тогда читаете самоучитель — вы и так гуру)
Разберем подробнее способы доступа и свойства элементов DOM.
Доступ к элементам
Любой доступ и изменения DOM берут свое начало от объекта document
.
Начнем с вершины дерева.
document.documentElement
Самый верхний тег. В случае корректной HTML-страницы, это будет <html>
.
document.body
Тег <body>
, если есть в документе (обязан быть).
Это свойство работает немного по-другому, если установлен DOCTYPE Strict. Обычно проще поставить loose DOCTYPE.
Следующий пример при нажатии на кнопку выдаст текстовое представление объектов 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
в правом верхнем углу.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <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>
Здесь показаны только элементы внутри body
, т.к только они отображаются на странице. Для элементов типа 1 (теги) в скобочках указан соответствующий тег, для текстовых элементов (тип 3) — стоит просто цифра.
Дочерние элементы
С вершины дерева можно пойти дальше вниз. Для этого каждый DOM-узел содержит массив всех детей, отдельно — ссылки на первого и последнего ребенка и еще ряд полезных свойств.
-
Все дочерние элементы, включая текстовые находятся в массиве
childNodes
.В следующем примере цикл перебирает всех детей
document.body
.for(var i=0; i<document.body.childNodes.length; i++) { var child = document.body.childNodes[i] alert(child.tagName) }
-
Свойства
firstChild
иlastChild
показывают на первый и последний дочерние элементы и равныnull
, если детей нет. -
Свойство
parentNode
указывает на родителя. Например, для<body>
таким элементом является<html>
:alert(document.body.parentNode == document.documentElement) // true
- Свойства
previousSibling
иnextSibling
указывают на левого и правого братьев узла.
В общем. если взять отдельно <body>
с детьми из нормализованного DOM — такая картинка получается ОТ <body>
:
И такая — для ссылок наверх и между узлами:
- Синяя линия — массив
childNodes
- Зеленые линии — свойства
firstChild
,lastChild
. - Красная линия — свойство
parentNode
- Бордовая и лавандовая линии внизу —
previousSibling
,nextSibling
Этих свойств вполне хватает для удобного обращения к соседям.
Свойства элементов
У DOM-элементов есть масса свойств. Обычно используется максимум треть из них. Некоторые из них можно читать и устанавливать, другие — только читать.
Есть еще и третий вариант, встречающийся в IE — когда устанавливать свойство можно только во время создания элемента.
Рассмотрим здесь еще некоторые (не все) свойства элементов, полезные при работе с 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
в обработчике события является текущий элемент (т.е сама кнопка). Подробнее об этом — во введении в события.
Есть общее правило замены — если CSS-атрибут имеет дефисы, то для установки style
нужно заменить их на верхний регистр букв.
Например, для установки свойства z-index
в 1000, нужно поставить:
element.style.zIndex = 1000
innerHTML
Когда-то это свойство поддерживалось только в IE. Теперь его поддерживают все современные браузеры.
Оно содержит весь HTML-код внутри узла, и его можно менять.
Свойство innerHTML применяется, в основном, для динамического изменения содержания страницы, например:
document.getElementById('footer').innerHTML = '<h1>Bye!</h1> <p>See ya</p>'
Пожалуй, innerHTML — одно из наиболее часто используемых свойств DOM-элемента.
className
Это свойство задает класс элемента. Оно полностью аналогично html-атрибуту «class».
elem.className = 'newclass'
onclick
, onkeypress,
onfocus
…
.. И другие свойства, начинающиеся на «on…», хранят функции-обработчики соответствующих событий. Например, можно присвоить обработчик события onclick
.
Подробнее об этих свойствах и обработчиках событий — см. введение в события.
When I started out as a web developer, people often threw around the term «DOM» in the industry. Every JavaScript tutorial mentioned it, but I didn’t know what it meant.
Fast forward two years, and now that I know what it’s all about, I am going to explain what the Document Object Model is in plain and simple English.
What is the DOM?
Imagine this: you have the TV on. You don’t like the show that’s being streamed, and you want to change it. You also want to increase its volume.
To do that, there has to be a way for you to interact with your television. And what do you use to do that?
A remote.
The remote serves as the bridge which allows you interact with your television.
You make the TV active and dynamic via the remote. And in the same way, JavaScript makes the HTML page active and dynamic via the DOM.
Just like how the television can’t do much for itself, JavaScript doesn’t do much more than allow you to perform some calculations or work with basic strings.
So to make an HTML document more interactive and dynamic, the script needs to be able to access the contents of the document and it also needs to know when the user is interacting with it.
It does this by communicating with the browser using the properties, methods, and events in the interface called the Document Object Model, or DOM.
For example, say that you want a button to change colours when it gets clicked or an image to slide when the mouse hovers over it. First, you need to reference those elements from your JavaScript.
The DOM is a tree-like representation of the web page that gets loaded into the browser.
It represents the web page using a series of objects. The main object is the document object, which in turn houses other objects which also house their own objects, and so on.
The Document Object
This is the top most object in the DOM. It has properties and methods which you can use to get information about the document using a rule known as dot notation.
After the document, you place a dot followed by a property or method.
Let’s look at a simple demonstration that shows how a script can access the contents of an HTML document through the DOM:
<h1>Login to your account</h1>
<form name=”LoginFrm” action=”login.php” method=”post”>Username
<input type=”text” name=”txtUsername” size=”15”/> <br/>Password
<input type=”password” name=”numPassword” size=”15”/> <br/>
<input type=”submit” value=”Log In” />
</form>
<p> New user? <a href=”register.php”> Register here</a>
<a href=”lostPassword.php”> Retrieve password </a>
</p>
var username = document.LoginFrm.txtUsername.value //Gets the username input
Alright. That’s the HTML a login form. You can access all of these elements in JavaScript with the set of properties and methods the DOM API provides. But what are those methods?
In addition to the property and method included in the code snippet, let’s take a look at some of the other popular ones:
The querySelectorAll() method
You use this method to access one or more elements from the DOM that matches one or more CSS selectors:
<div> first div </div>
<p> first paragraph </p>
<div> second div </p>
<p> second paragraph </p>
<div> another div </div>
var paragraphs = document.querySelectorAll( 'p' );
paragraphs.forEach(paragraph => paragraph.display = 'none')
The createElement() method
You use this method to create a specified element and insert it into the DOM:
<div>first div</div>
<p> first paragraph</p>
<div>second div</div>
<p>second paragraph</p>
<div>another div</div>
var thirdParagraph = document.createElement('p');
The getElementById() method
You use this method to get an element from the document by its unique id attribute:
<div id='first'> first div </div>
<p> first paragraph</p>
<div>second div</div>
<p> second paragraph</p>
<div>another div</div>
var firstDiv = getElementById("first")
The getElementsByTagname() method
You use this method to access one or more elements by their HTML tag name:
<div> first div </div>
<p> first paragraph</p>
<div> second div</div>
<p>second paragraph</p>
<div>another div</div>
divs = document.getElementByTagname("div");
The appendChild() element
You use this element to access one or more elements by their HTML tag name.
It adds an element as the last child to the HTML element that invokes this method.
The child to be inserted can be either a newly created element or an already existing one. If it already exists, it will be moved from its previous position to the position of the last child.
<div
<h2>Mangoes</h1>
</div>
var p = document.createElement( 'p' );
var h2 = document.querySelector( 'h2' );
var div = document.querySelector( 'div' );
h1.textContent = 'Mangoes are great...'
div.appendChild('p');
The innerHTML property
You use this property to access the text content of an element.
The addEventListener() property
This property attaches an event listener to your element.
It takes a callback which will run when that event is triggered.
<button>Click to submit</button>
var btn = document.querySelector( 'button' );
btn.addEventListener( 'click' ,foo);
function foo() { alert( 'submitted!' );
btn.innerHTML = '';
}
The replaceChild() property
This property replaces one child element with another new or existing child element. If it already exists, it will be moved from its previous position to the position of the last child.
<div>
<h1>Mangoes</h1>
</div>
var h2 = document.createElement( 'h2' );
var h1 = document.querySelector( 'h1' );
var div = document.querySelector( 'div' );
h2.textContent = 'Apple';
div.insertBefore(h1, h2);
The setAttribute() method
You use this method to set or change the value of an element’s attribute.
Suppose we have an attribute “id” containing the value “favourite.” But we want to change the value to “worst” Here’s how you can do that with code:
<div>
<h1 id='favourite'>Mangoes</h1>
</div>
var h1 = document.querySelector( 'h1' );
h1.setAttribute(div, 'worst');
The node method
Every element in an HTML page is known as a node.
You can access any element by using the following properties with the node object:
node.childNodes
– accesses the child nodes of a selected parentnode.firstChild
– accesses the first child of a selected parentnode.lastChild
– accesses the last child of a selected parent.node.parentNode
– accesses the parent of a selected child node.node.nextSibling
– accesses the next consecutive element (sibling) of a selected element.node.previousSibling
– accesses the previous element (sibling) of a selected element
<ul id-“list”>
<li><a href= ”about.html”class = ”list_one”> About</a></li>
<li><a href= ”policy.html”> Policy</a></ li>
<li><a href= ”map.html”> Map</a></ li>
<li><a href= ”Refund.html”> Refund</a></li>
</ul>
var list = document.getElementsById( “site-list” )
var firstItem = list.childNodes[0].childNodes[0];
Summary
The DOM is a top down representation of all the elements that make up a web page. It’s the interface through which your script interacts with your HTML.
There are many properties and methods which you can use to get information about the DOM and manipulate it.
That’s all for this article. I hope you learnt something worthwhile.
If you liked it, you can buy me some coffee here.
Thank you and see you soon.
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started
Iqra Masroor
Posted on
• Updated on
DOM or Document Object Model is a representation of the web page or document, which can be modified with a scripting language such as JavaScript according to MDN.
It characterizes the DOM HTML document as a hierarchical tree structure and each element in the document tree is called a Node.
DOM nodes represent all components that make up a web page. For example, a head tag is considered to be a node. Most nodes have a starting tag and ending tag. Things can be nested inside these tags. The inner node is called a child node and the outer node is considered to be its parent node.
Some nodes are self-closing tags like the «img» tag. These are called void nodes and cannot be a parent node, meaning things can’t be nested within them.
Refer to the graph below.
Since ‘document’ is an object which has properties and attributes, it will have properties & methods. In order to access things within an object, we use selector and query methods to change the content displayed in the browser.
Element Selectors
document.getElementById("idName")
//This method only returns the one element by the specified ID.
document.getElementByClass("className")
//This method returns all elements inside the whole document by the class you specified.
document.getElementById("someElement").getElementsByClassName("className")
//It works in all elements of the DOM, this will return all elements by the class you specify inside the element you want
Enter fullscreen mode
Exit fullscreen mode
Query Selectors
document.querySelector("#idName")
//This method takes one argument, which is a CSS selector & returns the first element that matches the selector.
document.querySelectorAll(".className")
//Works similar to above; returns a node list collection of all matching elements.
Enter fullscreen mode
Exit fullscreen mode
Create an Element
APPEND
document.createElement("body")
//this method creats the element, but it does not show up on the page.
document.body.append(element)
//this method gets the element to appear on the page.
Enter fullscreen mode
Exit fullscreen mode
.INNERHTML
<h1 id="greetings"> HELLO </h1>
let element = document.querySelector("#greeting")
element.innerHTML = "Welcome"
//selects the h1 called greetings and changes HELLO to welcome
Enter fullscreen mode
Exit fullscreen mode
Changing Attributes
const element = document.querySelector(".container")
element.style.backgroundColor="#f0f0f0"
//changes the selected elements(in this case the container class) color to grey
Enter fullscreen mode
Exit fullscreen mode
Removing Elements
element.remove()
//removes a whole element from the page
Enter fullscreen mode
Exit fullscreen mode
This is just a basic overview of some of the methods used to manipulate the DOM.
For many years we have relied on third-party JavaScript libraries such as jQuery to write JavaScript for the web. However, in recent years, the DOM API has evolved a lot, so adding dynamic functionality to web pages using native JavaScript is becoming easier for developers.
This article is a high-level overview of the DOM API built into every modern web browser. We’ll look into what the DOM API is, how it’s related to JavaScript, and how you can use it to write JavaScript for the web.
What Is the DOM?
DOM stands for Document Object Model, and it’s the data representation of a web document, such as an HTML page, an SVG graphic, or an XML file.
In very plain English, the DOM is a bunch of objects (Document Object Model) that make up a web page. Each object is the real-world implementation of an interface — the blueprint that describes what an object should look like. Thus, one interface can be implemented by multiple objects.
For example, each HTML <form>
element is represented by a HTMLFormElement
object in the DOM, which is an implementation of the HTMLFormElement
interface. This interface comes with pre-written properties (interface variables) and methods (interface functions) that you can use to add dynamic functionality to each form object (that appears as a <form>
tag in the HTML document). For example, you can use HTMLFormElement.reset()
method to reset a form to its initial state.
Essentially, that’s how the entire DOM works. The most basic interface is called Node
— every HTML element, text string, and the document itself is a node too. Other interfaces extend the Node
interface and inherit its properties and methods, but as each interface can have children, inheritance works at multiple levels. For example, HTMLFormElement
is a child interface of HTMLElement
, which is a child interface of Element
, which is a child interface of Node
.
The web browser arranges all of these nodes into a document tree (DOM tree) that represents the nodes and the relationships between them as a tree hierarchy.
What is the DOM API?
While there are many DOMs (each HTML, XML, and SVG document has a DOM), there’s just one DOM API, which is a W3C specification. The DOM API is written in JavaScript, and you can use it to manipulate the DOM of a web document using JavaScript.
API stands for “Application Programming Interface”, and it’s a collection of classes, interfaces, properties, methods, and other code structures that developers can use to access the functionality of an application. For example, you can use the Twitter API to access tweets, users, messages, ads, and other elements of the Twitter application programmatically. Similarly, you can use web APIs to use the built-in functionalities of a web browser (which is an application too).
The DOM API is one of the multiple web APIs built into web browsers. There are lower-level APIs such as the Web Workers API (for background operations) and higher-level ones such as the DOM.
Most web APIs, including the DOM API, are written in JavaScript. First, the W3C creates the specifications, then browser vendors implement them. Thus, “browser support” refers to whether a browser has implemented a specific functionality of the DOM API or not. If browser support is good, you can safely use an object (interface implementation), property, or method in production, while if it’s poor, it’s better to find an alternative (you can check browser support on the CanIUse website).
HTML tags and attributes and CSS styles all have their handles in the DOM API so that you can modify them dynamically. For example, the following HTML tag:
1 |
<a href='page.html' target='_blank'>Click this link</a> |
is represented by four nodes (one parent and three children) in the DOM:
- (parent node) an
HTMLAnchorElement
object in the DOM (that’s the implementation of theHTMLAnchorElement
interface); this is an element node - (child node) an
HTMLAnchorElement.href
property defined by theHTMLAnchorElement
interface; this is an attribute node - (child node) a
HTMLAnchorElement.target
property; this is another attribute node - (child node) a text node (“Click this link”)
Now, let’s see how you can use the DOM API in practice.
An Example of Using the DOM API
Let’s imagine you want to change the text color of the link above dynamically from the default blue to magenta.
If you analyze the documentation of the HTMLAnchorElement
interface, you won’t find any properties or methods that you can use for this purpose.
But, as HTMLAnchorElement
is a child interface of HTMLElement
, it inherits its properties and methods too. This interface has an HTMLElement.style
property that lets you change the CSS belonging to an HTML element. If you read its description in the docs, you’ll see that it can use properties from the CSS Properties Reference, also defined by the DOM API.
In our example, we’ll need to use the color
property, so we’ll write the following code:
1 |
element.style.color = 'magenta'; |
However, if we run this code, it will return a ReferenceError
exception in the console because element
is not defined. Before we can manipulate a DOM element with JavaScript, we need to select it using the DOM API and pass it to a variable.
To select nodes in a document, we’ll need to go to the Document
interface. This interface has two methods we can use, Document.querySelector()
that finds the first instance of a selector and Document.querySelectorAll()
that finds all instances of a selector. Here, we’ll use querySelector()
, as we have just one anchor element on the page. So, the new code is:
1 |
// selects the first 'a' element on the page
|
2 |
let element = document.querySelector('a'); |
3 |
|
4 |
// sets the 'color' property to 'magenta'
|
5 |
element.style.color = 'magenta'; |
Now, this code will work properly and change the link color to magenta. There are a few things to note here:
- as
style.color
is a property, we can set its value by using the assignment operator (= sign) - as
document.querySelector()
is a method (indicated by the parentheses after it), we can define its value by adding a parameter('a')
within the parentheses - while we need to define the
element
variable, we don’t have to define thedocument
variable as it’s a global object similar towindow
(the belonging variable is built into the browser — note that the object is written with capital initial e.g.Document
, while the variable is written with all lowercase e.g.document
) - if we had more than one link on the page, we’d need to use the
querySelectorAll()
method instead ofquerySelector()
and place theelement.style.color
declaration within afor
loop to change the color of all links on the page:
1 |
<a href='page01.html' target='_blank'>Link 01</a> |
2 |
<a href='page02.html' target='_blank'>Link 02</a> |
3 |
<a href='page03.html' target='_blank'>Link 03</a> |
1 |
// selects all 'a' elements on the page
|
2 |
let elements = document.querySelectorAll('a'); |
3 |
|
4 |
// loops through the element object which is a NodeList
|
5 |
for (let i = 0; i < elements.length; i++) { |
6 |
|
7 |
// sets the 'color' property to 'magenta'
|
8 |
elements[i].style.color = 'magenta'; |
9 |
}
|
We need to loop through the elements
variable because querySelectorAll()
returns a NodeList
object that includes more than one item.
What Type of Object is Returned?
It’s always important to know what type of object a method returns, as it defines your options. For instance, as querySelector()
returns an Element
object that has just one element, we didn’t have to loop through it in the previous example.
You can either check the return type in the method’s documentation or by using the console.log()
method and run the code in the console of your browser’s DevTools.
For instance, you can check the return type of the elements
variable above with console.log(elements);
which returns the following value in the web console (in Chrome DevTools):
A Better Example of Using the DOM API
It frequently happens that the DOM API provides more than one solution for the same task. So, it’s always a good idea to ask whether we have used the best solution. For instance, in the example above we didn’t; we changed the style using JavaScript, which goes against the separation of concerns (SoC) software design principle.
It would be much more effective, both performance- and workflow-wise, if we added a class dynamically to each anchor tag we want to change the color of, then add the style rules using CSS. Luckily, the DOM has a solution for this, respectively the Element.classList.add()
method that we can use on every object that inherits from the generic Element
object.
The above example looks like this using this method:
1 |
// selects all 'a' elements on the page
|
2 |
let elements = document.querySelectorAll('a'); |
3 |
|
4 |
// loops through the elements variable
|
5 |
for (let i = 0; i < elements.length; i++) { |
6 |
|
7 |
// adds the 'dynamic-color' class to each element
|
8 |
elements[i].classList.add('dynamic-color'); |
9 |
}
|
Then, we can add the color with CSS:
1 |
.dynamic-color { |
2 |
color: magenta; |
3 |
}
|
Much nicer, I think. It’ll also be easier to change the link color if we want to.
Next Steps
Here ends our high-level overview of how the DOM API works!
To understand it better, have a look at my other articles on this subject: one about event listeners that let you bind dynamic functionality (e.g. the link color change in the example above) to events (e.g. a click) and another one about how to convert jQuery to JavaScript using the DOM API.
The Mozilla Developer Network also has great explainer articles, such as their Introduction to the DOM tutorial and the HTML DOM API page that explains how to use interfaces belonging to HTML element nodes.
Did you find this post useful?
Web developer & technical writer. Covering web design, web development, UX design, and accessibility.