BOM

Все объекты, через которые JavaScript взаимодействует с браузером, описываются таким понятием как Browser Object Model (Объектная Модель Браузера).

Browser Object Model можно представить в виде следующей схемы:

В вершине находится главный объект - объект window, который представляет собой браузер. Этот объект в свою очередь включает ряд других объектов, в частности, объект document, который представляет отдельную веб-страницу, отображаемую в браузере.

- Объект window

Объект window представляет собой окно веб-браузера, в котором размещаются веб-страницы. window является глобальным объектом, поэтому при доступе к его свойствам и методам необязательно использовать его имя. Например, window имеет метод alert(), который отображает окно сообщения. Но нам необязательно писать:

window.alert("Привет мир!");

window можно не использовать:

alert("Привет мир!");

Но так как данный объект глобальный, то это накладывает некоторые ограничения. Например:

var alert = function(message) {
    document.write("Сообщение: " + message);
};

window.alert("Привет мир!");  // Сообщение: Привет мир!

Все объявляемые в программе глобальные переменные или функции автоматически добавляются к объекту window. И поскольку название новой функции будет совпадать с названием метода alert(), то произойдет переопределение этого метода в объекте window новой функцией.

И если мы объявим в программе какую-нибудь глобальную переменную, то она нам доступна как свойство в объекте window:

var message = "hello";
document.write(window.message);

Управление окнами

- Диалоговые окна

Для взаимодействия с пользователем в объекте window определен ряд методов, которые позволяют создавать диалоговые окна.

Метод alert() выводит окно с сообщением.

Метод confirm() отображает окно с сообщением, в котором пользователь должен подтвердить действие двух кнопок OK и Отмена. В зависимости от выбора пользователя метод возвращает true (если пользователь нажал OK) или false (если пользователь нажал кнопку Отмены).

И метод prompt() позволяет с помощью диалогового окна запрашивать у пользователя какие-либо данные. Данный метод возвращает введенное пользователем значение. Если пользователь откажется вводить значение и нажмет на кнопку отмены, то метод возвратит значение null.

- Открытие, закрытие и позиционирование окон

Объект window также предоставляет ряд методов для управления окнами браузера. Так, метод open() открывает определенный ресурс в новом окне браузера:

var popup = window.open('https://microsoft.com', 'Microsoft', 'width=400, height=400, resizable=yes');

Метод open() принимает ряд параметров: путь к ресурсу, описательное название для окна и в качестве третьего параметра набор стилевых значений окна. Метод возвращает ссылку на объект нового окна.

Мы можем установить следующие стилевые характеристики:

  • width: ширина окна в пикселях. Например, width=640
  • height: высота окна в пикселях. Например, height=480
  • left: координата X относительно начала экрана в пикселях. Например, left=0
  • top: координата Y относительно начала экрана в пикселях. Например, top=0
  • titlebar: будет ли окно иметь строку с заголовком. Например, titlebar=no
  • menubar: будет ли окно иметь панель меню. Например, menubar=yes
  • toolbar: будет ли окно иметь панели инструментов. Например, toolbar=yes
  • location: будет ли окно иметь адресную строку. Например, location=no
  • scrollbars: допускается ли наличие полос прокрутки. Например, scrollbars=yes
  • status: наличие статусной строки. Например, status=yes
  • resizable: может ли окно изменять размеры. Например, resizable=no

С помощью метода close() можно закрыть окно. Например, откроем новое окно и через 5 секунд закроем его:

var popup = window.open('https://microsoft.com', 'Microsoft', 'width=400, height=400, resizable=yes');

function closeWindow() {
    popup.close();
}

setTimeout(closeWindow, 5000);

Метод moveTo() позволяет переместить окно на новую позицию:

var popup = window.open('https://microsoft.com', 'Microsoft', 'width=400, height=400, resizable=yes');
popup.moveTo(50, 50);

В данном случае окно перемещается на позицию с координатами x=50, y=50 относительно левого верхнего угла экрана.

Метод resizeTo() позволяет изменить размеры окна:

var popup = window.open('https://microsoft.com', 'Microsoft', 'width=400, height=400, resizable=yes');
popup.resizeTo(500, 350); // 500 - ширина и 350 - высота

Объект history

Объект history предназначен для хранения истории посещений веб-страниц в браузере. Нам этот объект доступен через объект window.

Все сведения о посещении пользователя хранятся в специальном стеке (history stack). С помощью свойства length можно узнать, как много веб-станиц хранится в стеке:

document.write("В истории " + history.length + " станиц");

Для перемещения по страницам в истории в объекте history определены методы back() (перемещение к прошлой посмотренной странице) и forward() (перемещение к следующей просмотренной странице).

history.back(); // перемещение назад

Также в объекте history определен специальный метод go(), который позволяет перемещаться вперед и назад по истории на определенное число страниц. Например, переместимся на 2 страницы назад:

history.go(-2);

Соответственно если надо переместиться на несколько страниц вперед, то в метод передается положительное значение. Например, переместимся вперед на три страницы:

history.go(3);

Объект location

Объект location содержит информацию о расположении текущей веб-страницы: URL, информацию о сервере, номер порта, протокол. С помощью свойств объекта мы можем получить эту информацию:

  • href: полная строка запроса к ресурсу
  • pathname: путь к ресурсу
  • origin: общая схема запроса
  • protocol: протокол
  • port: порт, используемый ресурсом
  • host: хост
  • hostname: название хоста
  • hash: если строка запроса содержит символ решетки (#), то данное свойство возвращает ту часть строки, которая идет после этого символа
  • search: если строка запроса содержит знак вопроса (?), например, то данное свойство возвращает ту часть строки, которая идет после знака вопроса

Например, пусть есть следующая веб-страница index.html, которая лежит на локальном веб-сервере:

document.write("Строка запроса: " + location.href + "<br />");
document.write("Путь к ресурсу: " + location.pathname + "<br />");
document.write("Схема: " + location.origin + "<br />");
document.write("Протокол: " + location.protocol + "<br />");
document.write("Порт: " + location.port + "<br />");
document.write("Хост: " + location.host + "<br />");
document.write("Имя хоста: " + location.hostname + "<br />");
document.write("Хэш: " + location.hash + "<br />");
document.write("Поиск: " + location.search + "<br />");

Результат:

Строка запроса: http://a-test.com/index.html?a=2&b=5
Путь к ресурсу: /index.html
Схема: http://a-test.com
Протокол: http:
Порт: 
Хост: a-test.com
Имя хоста: a-test.com
Хэш: 
Поиск: ?a=2&b=5

Также объект location предоставляет ряд методов, которые можно использовать для управления путем запроса:

  • assign(url): загружает ресурс, который находится по пути url
  • reload(forcedReload): перезагружает текущую веб-страницу. Параметр forcedReload указывает, надо ли использовать кэш браузера. Если параметр равен true, то кэш не используется
  • replace(url): заменяет текущую веб-станицу другим ресурсом, который находится по пути url. В отличие от метода assign, который также загружает веб-станицу с другого ресурса, метод replace не сохраняет предыдущую веб-страницу в стеке истории переходов history, поэтому мы не сможем вызвать метод history.back() для перехода к ней

Для перенаправления на другой ресурс мы можем использовать как свойства, так и методы location:

location = "http://google.com";
// аналогично
// location.href = "http://google.com";
// location.assign("http://google.com");

Переход на другой локальный ресурс:

location.replace("index.html");

Объект navigator

Объект navigator содержит информацию о браузере и операционной системе, в которой браузер запущен. Он определяет ряд свойств и методов, основным из которых является свойство userAgent, представляющее браузер пользователя:

document.write(navigator.userAgent);

Результат:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36

Чтобы вычленить из этой информации непосредственно браузер, можно попробовать найти в этой информации название браузера:

var browser, uAgent = navigator.userAgent;

if(uAgent.indexOf("Chrome") > -1) {
    browser = "Google Chrome";
} else if (uAgent.indexOf("Safari") > -1) {
    browser = "Apple Safari";
} else if (uAgent.indexOf("Opera") > -1) {
    browser = "Opera";
} else if (uAgent.indexOf("Firefox") > -1) {
    browser = "Mozilla Firefox";
} else if (uAgent.indexOf("MSIE") > -1) {
    browser = "Microsoft Internet Explorer";
}

document.write(browser);

Таймеры

Для выполнения действий через определенные промежутки времени в объекте window предусмотрены функции таймеров. Есть два типа таймеров: одни выполняются только один раз, а другие постоянно через промежуток времени.

- Функция setTimeout

Для одноразового выполнения действий через промежуток времени предназначена функция setTimeout(). Она может принимать два параметра:

var timerId = setTimeout(someFunction, period)

Параметр period указывает на промежуток, через который будет выполняться функция из параметра someFunction. А в качестве результата функция возвращает id таймера.

function timerFunction() {
    document.write("выполнение функции setTimeout");
}

setTimeout(timerFunction, 3000);

В данном случае через 3 секунды после загрузки страницы произойдет срабатывание функции timerFunction.

Для остановки таймера применяется функция clearTimeout().

function timerFunction() {
    document.write("выполнение функции setTimeout");
}

var timerId = setTimeout(timerFunction, 3000);
clearTimeout(timerId);

- Функция setInterval

Функции setInterval() и clearInterval() работают аналогично функциям setTimeout() и clearTimeout() с той лишь разницей, что setInterval() постоянно выполняет определенную функцию через промежуток времени.

Например, напишем небольшую программу для вывода текущего времени:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
	</head>
	<body>
		<div id="time"></div>
		
		<script>
			function updateTime() {
				document.getElementById("time").innerHTML = new Date().toTimeString();
			}
			
			setInterval(updateTime, 1000);
		</script>
	</body>
</html>

Здесь через каждую секунду (1000 миллисекунд) вызывается функция updateTime(), которая обновляет содержимое поля <div id="time">, устанавливая в качестве его кода html текущее вемя.

- requestAnimationFrame()

Метод requestAnimationFrame() действует аналогично setInterval() за тем исключением, что он больше заточен под анимации, работу с графикой и имеет ряд оптимизаций, которые улучшают его производительность.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<style>
			#rect {
				margin: 100px;
				width: 100px;
				height: 100px;
				background: #50c878;
			}
		</style>
	</head>
	<body>
		<div id="rect"></div>
		
		<script>
			var square = document.getElementById("rect");
			var angle = 0;
			
			function rotate() {
				angle = (angle + 2)%360;
				square.style.transform = "rotate(" + angle + "deg)";
				window.requestAnimationFrame(rotate);
			}
			
			var id = window.requestAnimationFrame(rotate);
		</script>
	</body>
</html>

В метод window.requestAnimationFrame() передается функция, которая будет вызываться определенное количество раз (обычно 60) в секунду. В данном случае в этот метод передается функция rotate, которая изменяет угол поворота блока на странице и затем обращается опять же к методу window.requestAnimationFrame(rotate).

В качестве возвращаемого результата метод window.requestAnimationFrame() возвращает уникальный id, который может потом использоваться для остановки анимации:

window.cancelAnimationFrame(id);

DOM

Одой из ключевых задач JavaScript является взаимодействие с пользователем и манипуляция элементами веб-страницы. Для JavaScript веб-страница доступна в виде объектной модели документа (Document Object Model) или сокращенно DOM. DOM описывает структуру веб-страницы в виде древовидного представления и предоставляет разработчикам способ получить доступ к отдельным элементам веб-станицы.

Важно не путать понятия BOM (Browser Object Model - объектная модель браузера) и DOM (объектная модель документа). Если BOM предоставляет доступ к браузеру и его свойствам в целом, то DOM предоставляет доступ к отдельной веб-странице или html-документу и его элементам.

Например, рассмотрим простейшую страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Page Title</title>
</head>
<body>
    <h2>Page Header</h2>
    <div>
        <h3>Block Header</h3>
        <p>Text</p>
    </div>
</body>
</html>

Дерево DOM для этой страницы будет выглядеть следующим образом:

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

Существует следующие виды узлов:

  • Element: html-элемент
  • Attr: атрибут html-элемента
  • Document: корневой узел html-документа
  • DocumentType: DTD или тип схемы XML-документа
  • DocumentFragment: место для временного хранения частей документа
  • EntityReference: ссылка на сущность XML-документа
  • ProcessingInstruction: инструкция обработки веб-страницы
  • Comment: элемент комментария
  • Text: текст элемента
  • CDATASection: секция CDATA в документе XML
  • Entity: необработанная сущность DTD
  • Notation: нотация, объявленная в DTD

Объект document

Для работы со структурой DOM в JavaScript предназначен объект document, который определен в глобальном объекте window. Объект document предоставляет ряд свойств и методов для управления элементами страницы.

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

Для поиска элементов на странице применяются следующие методы:

  • getElementById(value): выбирает элемент, у которого атрибут id равен value
  • getElementsByTagName(value): выбирает все элементы, у которых тег равен value
  • getElementsByClassName(value): выбирает все элементы, которые имеют класс value
  • querySelector(value): выбирает первый элемент, который соответствует css-селектору value
  • querySelectorAll(value): выбирает все элементы, которые соответствуют css-селектору value

Свойства объекта document

Кроме ранее рассмотренных методов объект document позволяет обратиться к определенным элементам веб-страницы через свойства:

  • documentElement: предоставляет доступ к корневому элементу <html>
  • body: предоставляет доступ к элементу <body> на веб-странице
  • images: содержит коллекцию всех объектов изображений (элементов img)
  • links: содержит коллекцию ссылок - элементов <a> и <area>, у которых определен атрибут href
  • anchors: предоставляет доступ к коллекции элементов <a>, у которых определен атрибут name
  • forms: содержит коллекцию всех форм на веб-странице

Эти свойства не предоставляют доступ ко всем элементам, однако позволяют получить наиболее часто используемые элементы на веб-странице. Например, получим корневой узел документа:

var container = document.documentElement;

Получим все изображения на странице:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
	</head>
	<body>
		<img src="picure1.png" alt="Картинка 1" />
		<img src="picure2.png" alt="Картинка 2" />
		<img src="picure3.png" alt="Картинка 3" />
		
		<script>
			var images = document.images;
			
			// изменим первое изображение
			images[0].src="pics/picture_4.jpg";
			images[0].alt="Новая картинка";
			
			// перебирем все изображения
			for(var i=0; i<images.length; i++) {			 
				document.write("<br/>" + images[i].src);
				document.write("<br/>" + images[i].alt);
			}
		</script>
	</body>
</html>

Подобно тому, как в коде HTML мы можем установить атрибуты у элемента img, так и в коде JavaScript мы можем через свойства src и alt получить и установить значения этих атрибутов.


Объект node

Каждый отдельный узел, будь то html-элемент, его атрибут или текст, в структуре DOM представлен объектом Node. Этот объект предоставляет ряд свойств, с помощью которых мы можем получить информацию о данном узле:

  • childNodes: содержит коллекцию дочерних узлов
  • firstChild: возвращает первый дочерний узел текущего узла
  • lastChild: возвращает последний дочерний узел текущего узла
  • previousSibling: возвращает предыдущий элемент, который находится на одном уровне с текущим
  • nextSibling: возвращает следующий элемент, который находится на одном уровне с текущим
  • ownerDocument: возвращает корневой узел документа
  • parentNode: возвращает элемент, который содержит текущий узел
  • nodeName: возвращает имя узла
  • nodeType: возвращает тип узла в виде числа
  • nodeValue: возвращает или устанавливает значение узла в виде простого текста

Создание, добавление и удаление элементов веб-станицы

Для создания элементов объект document имеет следующие методы:

  • createElement(elementName): создает элемент html, тег которого передается в качестве параметра. Возвращает созданный элемент
  • createTextNode(text): создает и возвращает текстовый узел. В качестве параметра передается текст узла
var elem = document.createElement("div");
var elemText = document.createTextNode("Привет мир");

Таким образом, переменная elem будет хранить ссылку на элемент div. Однако одного создания элементов недостаточно, их еще надо добавить на веб-страницу.

Для добавления элементов мы можем использовать один из методов объекта Node:

  • appendChild(newNode): добавляет новый узел newNode в конец коллекции дочерних узлов
  • insertBefore(newNode, referenceNode): добавляет новый узел newNode перед узлом referenceNode

Используем метод appendChild:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
	</head>
	<body>
		<div class="article">
			<h3>Заголовок статьи</h3>
			<p>Первый абзац</p>
			<p>Второй абзац</p>
		</div>
	
		<script>
			var articleDiv = document.querySelector("div.article");
			
			// создаем элемент
			var elem = document.createElement("h2");
			
			// создаем для него текст
			var elemText = document.createTextNode("Привет мир");
			
			// добавляем текст в элемент в качестве дочернего элемента
			elem.appendChild(elemText);
			
			// добавляем элемент в блок div
			articleDiv.appendChild(elem);
		</script>
	</body>
</html>

Сначала создаем обычный элемент заголовка h2 и текстовый узел. Затем текстовый узел добавляем в элемент заголовка. Затем заголовок добавляем в один из элементов веб-страницы.

Однако нам необязательно для определения текста внутри элемента создавать дополнительный текстовый узел, так как мы можем воспользоваться свойством textContent и напрямую ему присвоить текст:

var elem = document.createElement("h2");
elem.textContent = "Привет мир";

- Копирование элемента

Иногда элементы бывают довольно сложными по составу, и гораздо проще их скопировать, чем с помощью отдельных вызовов создавать их содержимое. Для копирования уже имеющихся узлов у объекта Node можно использовать метод cloneNode():

var articleDiv = document.querySelector("div.article");

// клонируем элемент articleDiv
var newArticleDiv = articleDiv.cloneNode(true);

// добавляем в конец элемента body
document.body.appendChild(newArticleDiv);

В метод cloneNode() в качестве параметра передается логическое значение: если передается true, то элемент будет копироваться со всеми дочерними узлами; если передается false - то копируется без дочерних узлов. То есть в данном случае мы копируем узел со всем его содержимым и потом добавляем в конец элемента body.

- Удаление элемента

Для удаления элемента вызывается метод removeChild() объекта Node. Этот метод удаляет один из дочерних узлов:

var articleDiv = document.querySelector("div.article");

// находим узел, который будем удалять - первый параграф
var removableNode = document.querySelectorAll("div.article p")[0];

// удаляем узел
articleDiv.removeChild(removableNode);

данном случае удаляется первый параграф из блока div.

- Замена элемента

Для замены элемента применяется метод replaceChild(newNode, oldNode) объекта Node. Этот метод в качестве первого параметра принимает новый элемент, который заменяет старый элемент oldNode, передаваемый в качестве второго параметра.


Объект element

Кроме методов и свойств объекта Node в JavaScript мы можем использовать свойства и методы объектов Element. Важно не путать эти два объекта: Node и Element. Node представляет все узлы веб-станицы, в то время как объект Element представляет непосредственно только html-элементы. То есть объекты Element - это фактически те же самые узлы - объекты Node, у которых тип узла (свойство nodeType) равно 1.

Одним из ключевых свойств объекта Element является свойство tagName, которое возвращает тег элемента. Например, получим все элементы, которые есть на странице:

function getChildren(elem) {
    for(var i in elem.childNodes) {
        if(elem.childNodes[i].nodeType === 1) {
            console.log(elem.childNodes[i].tagName);
            getChildren(elem.childNodes[i]);
        }
    }
}

var root = document.documentElement;
console.log(root.tagName);
getChildren(root);

Здесь вначале получаем тело корневого элемента <html> и затем с помощью рекурсивной функции getChildren получаем все вложенные элементы.

- Свойства innerText и innerHTML

Для получения или установки текстового содержимого элемента мы можем использовать свойство innerText, а для получения или установки кода html - свойство innerHTML.

Надо отметить, что свойство innerText во многом аналогично свойству textContent. То есть следующие вызовы будут равноценны:

var pElement = document.querySelectorAll("div.article p")[0];
pElement.innerText = "hello";
pElement.textContent = "hello";

Установка кода html у элемента:

var articleDiv = document.querySelector("div.article");
articleDiv.innerHTML ="<h2>Hello World!!!</h2><p>bla bla bla</p>";

- Методы объекта Element

Среди методов объекта Element можно отметить методы управления атрибутами:

  • getAttribute(attr): возвращает значение атрибута attr
  • setAttribute(attr, value): устанавливает для атрибута attr значение value. Если атрибута нет, то он добавляется
  • removeAttribute(attr): удаляет атрибут attr и его значение

- Размеры и позиция элементов

Элементы имеют ряд свойств, которые позволяют определить размер элемента. Но важно понимать разницу между всеми этими свойствами.

Свойства offsetWidth и offsetHeight определяют соответственно ширину и высоту элемента в пикселях. В ширину и высоту включается граница элемента.

Свойства clientWidth и clientHeight также определяют ширину и высоту элемента в пикселях, но уже без учета границы.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<style>
			#rect {
				width: 100px;
				height: 100px;
				background: #50c878;
				border: 3px solid silver;
			}
		</style>
	</head>
	<body>
		<div id="rect"></div>
		
		<script>
			var rect = document.getElementById("rect");
			
			console.log("offsetHeight: " + rect.offsetHeight);  // 106
			console.log("offsetWidth: " + rect.offsetWidth);  // 106
			console.log("clientHeight: " + rect.clientHeight);  // 100
			console.log("clientWidth: " + rect.clientWidth);  // 100
		</script>
	</body>
</html>

Поскольку у блока div определена граница в 3 пикселя, то по сравнению с clientHeight/clientWidth к offsetHeight/offsetWidth добавляет по 6 пикселей.


Изменение стиля элементов

Для работы со стилевыми свойствами элементов в JavaScript применяются, главным образом, два подхода:

  • Изменение свойства style
  • Изменение значения атрибута class

- Свойство style

Свойство style представляет сложный объект для управления стилем и напрямую сопоставляется с атрибутом style html-элемента. Этот объект содержит набор свойств CSS: element.style.свойствоCSS. Например, установим цвет шрифта:

var root = document.documentElement;

// устанавливаем стиль
root.style.color = "blue";

// получаем значение стиля
document.write(root.style.color); // blue

В данном случае название свойства color совпадает со свойством css. Аналогично мы могли бы установить цвет с помощью css:

html {
    color:blue;
}

Однако ряд свойств css в названиях имеют дефис, например, font-family. В JavaScript для этих свойств дефис не употребляется. Только первая буква, которая идет после дефиса, переводится в верхний регистр:

var root = document.documentElement;
root.style.fontFamily = "Verdana";

- Свойство className

С помощью свойства className можно установить атрибут class элемента html. Например:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<style>
			.blueStyle {
				color:blue;
				font-family:Verdana;
			}
			.article {
				font-size:20px;
			}
		</style>
	</head>
	<body>
		<div class="article">
			<h3>Заголовок статьи</h3>
			<p>Первый абзац</p>
			<p>Второй абзац</p>
		</div>
		
		<script>
			var articleDiv = document.querySelector("div.article");
			
			// установка нового класса
			articleDiv.className = "blueStyle";
			
			// получаем название класса
			document.write(articleDiv.className);
		</script>
	</body>
</html>

Благодаря использованию классов не придется настраивать каждое отдельное свойство css с помощью свойства style.

Но при этом надо учитывать, что прежнее значение атрибута class удаляется. Поэтому, если нам надо добавить класс, надо объединить его название со старым классом:

articleDiv.className = articleDiv.className + " blueStyle";

И если надо вовсе удалить все классы, то можно присвоить свойству пустую строку:

articleDiv.className = "";

- Свойство classList

Выше было рассмотрено, как добавлять классы к элементу, однако для управления множеством классов гораздо удобнее использовать свойство classList. Это свойство представляет объект, реализующий следующие методы:

  • add(className): добавляет класс className
  • remove(className): удаляет класс className
  • toggle(className): переключает у элемента класс на className. Если класса нет, то он добавляется, если есть, то удаляется