Работа с DOM, браузерные события, работа с сетью

Технопарк, весна, 2019 г.

Работа с DOM в браузере
Слайды доступны по ссылке
frontend-park-mailru.firebaseapp.com

Дисклеймер

Все практические примеры заливаются на git. Практика на лекциях демонстрирует самый простой подход к решению задач. Вам необходимо думать головой и пытаться сделать лучше, чем у нас

Мы всегда готовы помочь и дать совет, как это сделать наиболее хорошо

Итак, начнём!

Глобальные объекты и глобальная
область видимости

Глобальными называют переменные и функции, которые не находятся внутри какой-то функции или блока

В JavaScript все глобальные переменные и функции являются свойствами специального объекта, который называется «глобальный объект» (global object). Присваивая или читая глобальную переменную, мы, фактически, работаем со свойствами глобального объекта.

Глобальные объекты

В браузере это объект window

		var foo = 42;
		console.log(window.foo); // 42
		 
		const bar = 'top kek';
		console.log(window.bar); // undefined
		 
		window.baz = 'JavaScript <3';
		console.log(baz); // JavaScript <3
		 
	

Глобальные объекты

В Node.js тоже есть глобальный объект global

		console.log(global);
		 
		var foo = 42;
		console.log(global.foo); // 42
		 
	

Глобальные объекты

В глобальной области видимости this ссылается на глобальный объект

		// в браузере
		console.log(this === window); // true
		 
	

Глобальные объекты

В Node.js не всё так просто (попробуйте разобраться почему)

		// script.js
		console.log(this === global);
		 
	
		node script.js // false
		node -p 'console.log(this === global)' // true
		 
	

Объекты в JavaScript

Нативные объекты

Нативными (native object) объектами в JS называют объекты, свойства и поведение которых описаны в спецификации языка JavaScript. Их наличие не зависит от того окружения, где запускается код

Например Object, Array, Infinity, Date, Math, parseInt, eval...

Хост объекты

Хост (host object) объектами в JS называют объекты, которые предоставляются окружением (зависят от того, где работает код)

Например, для браузеров это будут document, location, history, XMLHttpRequest, setTimeout, setInterval... Для Node.js хост-объекты будут другими, например process

BOM — Browser Object Model

DOM — Document Object Model

Объектная Модель Документа (DOM) — это программный интерфейс (API) для HTML и XML документов. DOM предоставляет структурированное представление документа и то как эта структура может быть доступна из программ. По существу представление DOM соединяет веб страницу с языком JavaScript, позволяя изменять содержимое, стиль и структуру документа

DOM-дерево

DOM-tree

DOM — Document Object Model

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

		document // хост-объект, который даёт доступ к DOM
		 
		document.documentElement // узел HTML
		document.head // узел HEAD
		document.body // узел BODY
		 
	

Перебор узлов DOM

		element.parentNode // родительская нода
		 
		element.childNodes // дочерние ноды
		element.firstChild // первый ребёнок
		element.lastChild // последний ребёнок
		 
		document.previousSibling // предыдущий "сосед"
		document.nextSibling // следующий "сосед"
		 
	

Перебор элементов DOM

		element.parentElement // родитель-элемент
		element.children // только дочерние узлы-элементы
		 

		// первый и последний дети-элементы
		element.firstElementChild и element.lastElementChild
		 

		// соседи-элементы
		document.previousElementSibling и document.nextElementSibling
		 
	

Дополнительные возможности

У конкретных элементов DOM могут быть свои дополнительные ссылки для большего удобства навигации

		const elements = formElement.elements // все поля формы
		elements[1] // выбор поля по порядку
		elements['username'] // выбор поля по имени
		 
	

Дополнительные возможности

У конкретных элементов DOM могут быть свои дополнительные ссылки для большего удобства навигации

		tableElement.rows // коллекция строк TR таблицы
		tableElement.caption // элемент CAPTION таблицы
		tableElement.tBodies // коллекция элементов таблицы TBODY
		 
		tableRowElement.cells // коллекция ячеек строки таблицы
		tableRowElement.rowIndex // номер строки в таблице
		 
	

Поиск элементов по ID/name

		// вернёт элемент с ID 'my-header'
		document.getElementById('my-header')
		 
		// вернёт все элементы с атрибутом name="username"
		document.getElementsByName('username')
		 
	

Поиск элементов по имени тега

		// во всём документе
		document.getElementsByTagName('h1')
		 
		// среди потомков какого-либо элемента
		element.getElementsByTagName('h1')
		element.getElementsByTagName('*') // все элементы
		 
	

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

		// во всём документе
		document.getElementsByClassName('red-button')
		 
		// среди потомков какого-либо элемента
		element.getElementsByClassName('red-button')
		 
	

Поиск по CSS-селектору

		// во всём документе
		document.querySelectorAll('input[type=button].red-button')
		 
		// среди потомков какого-либо элемента
		element.querySelectorAll('input[type=button].red-button')
		 
		// ищет только первый элемент
		element.querySelector('input[type=button].red-button')
		 
	

Методы getElementsBy*(...)
возвращают живую коллекцию

Pезультат запросов getElementsBy* — это не массив (Array), а специальный объект, имеющий тип NodeList или HTMLCollection. Он похож на массив, так как имеет нумерованные элементы и длину, но внутри это не готовая коллекция, а «живой поисковой запрос»

Собственно поиск выполняется только при обращении к элементам коллекции или к её длине

Классы, иерархия DOM

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

		<h1>Ссылка: <a href="/">тут</a></h1>
	
		element.tagName // имя тега
		h1.tagName // "H1"
		 

		element.innerHTML // Внутреннее содержимое узла-элемента в виде HTML
		h1.innerHTML // "Ссылка: <a href="/">тут</a>"
		 
	

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

		<h1>Ссылка: <a href="/">тут</a></h1>
	
		element.outerHTML // Полный HTML узла-элемента
		h1.outerHTML // "<h1>Ссылка: <a href="/">тут</a></h1>"
		 

		element.textContent // Содержит только текст внутри элемента,
		                    // за вычетом всех тегов
		h1.textContent // "Ссылка: "
		 
	

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

		<h1>Ссылка: <a href="/">тут</a></h1>
	
		element.hidden // Атрибут, отвечающий за видимость элемента
		h1.hidden = true // скроет заголовок
		 

		link.href // Адрес ссылки
		input.value // Значение, введённое в текстовое поле
		 
	

Атрибуты элементов

		element.hasAttribute(name) // проверяет наличие атрибута
		element.getAttribute(name) // получает значение атрибута
		element.setAttribute(name, value) // устанавливает атрибут
		element.removeAttribute(name) // удаляет атрибут
		 
		elem.attributes // получить все атрибуты
		 
	

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

		document.createElement(tag) // создаёт элемент с тегом tag
		document.createTextNode(value) // создает текстовый узел
		element.cloneNode(deep) // клонирует элемент
		 
		parent.appendChild(el) // вставляет узел в конец
		parent.removeChild(el) // удаляет узел
		parent.replaceChild(newEl, oldEl) // заменяет узел
		parent.insertBefore(elem, nextSibling) // вставляет узел
		 
	

Браузерные события

События

Для реакции на действия посетителя и внутреннего взаимодействия скриптов существуют события

Событие — это сигнал от браузера о том, что что-то произошло. Существует много видов событий

События

События мыши:

События

События на элементах управления:

Клавиатурные события:

Список всех браузерных событий (~200 штук)

Обработчики событий

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

Добавление обработчиков
через атрибут

		<button onclick="this.parentElement.innerHTML+=
		    <button onclick="this.parentElement.innerHTML+='<span>click</span>'">Нажми меня</button> 
	

Добавление обработчиков
через свойства DOM-объекта

		let count = 0;
		const element = document.getElementsByTagName('BUTTON')[0];
		element.onclick = function() {
		    element.innerHTML = `Кликнуто ${++count} раз(а)`;
		}
		 
	

addEventListener и removeEventListener

		// добавление обработчика
		element.addEventListener(event, handler);
		 
		// удаление обработчика
		// нужно передать те же аргументы, что были у addEventListener
		element.removeEventListener(event, handler);
		 
	

Подробнее про addEventListener

Объект события event

		element.addEventListener('click', function (event) {
		    console.log(`${event.type} на ${event.currentTarget}`);
		    console.log(`${event.clientX}:${event.clientY}`);
		});
		 
	

Распространение событий
(events propagation)

Фазы обработки событий

Фазы обработки событий

Фазы обработки событий

Фазы обработки событий

Фазы обработки событий

Строго говоря, стандарт выделяет целых три стадии прохода события:

Перехват события на определённой
стадии

Используем третий аргумент addEventListener

		// добавление обработчика
		element.addEventListener(event, handler, phase);
		 
		// phase === true - событие будет перехвачено по дороге вниз
		// phase === false - событие будет поймано при всплытии
		 
		// при удалении нужно передать те же аргументы,
		// что были у addEventListener
		element.removeEventListener(event, handler, phase);
		 
	

Предотвращение обработки
событий

		event.preventDefault(); // предотвращает поведение по умолчанию
		event.stopPropagation(); // предотвращает всплывание события
		                         // (все обработчики на этом элементе выполнятся)
		 
		// останавливает обработку событий на текущем элементе
		// (все остальные обработчики на этом элементе не выполнятся)
		event.stopImmediatePropagation();
		 
	

Более гибкое добавление
обработчиков

В современных браузерах в третьем аргументе addEventListener можно передавать объект с настройками

		// добавление обработчика
		element.addEventListener(event, handler, options);
		 
		// options.capture - перехват события по дороге вниз
		// options.once - обработчик будет вызван только один раз
		// options.passive - говорит браузеру, что внутри обработчика не будет
		//                   вызова preventDefault
		 
		// при удалении нужно передать те же аргументы
		element.removeEventListener(event, handler, options);
		 
	

SPA — Single Page Application

Одностраничное приложение (SPA) — это веб-приложение, использующее единственный HTML-документ как оболочку для всех веб-страниц и организующий взаимодействие с пользователем через динамически подгружаемые ресурсы и данные, обычно посредством AJAX

		<html>
		    <head> ... </head>
		    <body>
		        <div id="application"></html>
		    </body>
		</html>
		 
	

Практика!

Работа с сетью

Протокол HTTP

HTTP (HyperText Transfer Protocol — «протокол передачи гипертекста») — протокол прикладного уровня передачи данных (изначально — в виде гипертекстовых документов в формате «HTML»), в настоящий момент используется для передачи произвольных данных

HTTP — текстовый протокол (имеются в виду версии протокола до версии HTTP/2)

Протокол HTTP

HTTP запрос

		// METHOD URI HTTP/VERSION <= стартовая строка
		GET /lib/slides.css HTTP/1.1
		Host: frontend-park-mailru.firebaseapp.com
		Content-Type: text/css; charset=utf-8
		другие заголовки...
		 
		        тело запроса (опционально)
		 
	

HTTP ответ

		// HTTP/VERSION STATUS_CODE REASON_PHRASE <= стартовая строка
		HTTP/1.0 200 OK
		Host: frontend-park-mailru.firebaseapp.com
		Content-Type: text/css; charset=utf-8
		другие заголовки...
		 
		        тело ответа (опционально)
		 
	

Работа с XMLHttpRequest

		// 1. Создаём новый объект XMLHttpRequest
		const xhr = new XMLHttpRequest();
		// 2. Конфигурируем его: GET-запрос на URL '/lib/slides.css'
		xhr.open('GET', '/lib/slides.css', false);
		 
		// 3. Отсылаем запрос
		xhr.send();
		 
	

Работа с XMLHttpRequest

		// 4. Если код ответа сервера не 200, то это ошибка
		console.log(`Ответ от сервера: ${xhr.status} ${xhr.statusText}`);
		if (xhr.status === 200) {
		    console.log(xhr.responseText);
		} else {
		    console.error('Ошибка!')
		}
		 
	

Пример POST-запроса

		const xhr = new XMLHttpRequest();
		xhr.open('POST', '/post', false);
		 
		// Выставляем заголовок
		xhr.setRequestHeader('Content-Type', 'text/plain; charset=utf-8');
		xhr.send('Request payload');
		// ждём ответа
		 
	

Отправка json в теле запроса

		const xhr = new XMLHttpRequest();
		xhr.open('POST', '/post', false);
		 
		// Выставляем заголовок
		xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
		xhr.send(JSON.stringify({name: 'Alex'});
		// ждём ответа
		 
	

Отправка бинарных данных в
теле запроса

		const xhr = new XMLHttpRequest();
		xhr.open('POST', '/post', false);
		 
		const binary = new Uint8Array(1024 * 1024); // 1 MB данных
		const blob = new Blob([binary], {type: 'application/octet-stream'});
		xhr.send(blob);
		// ждём ответа
		 
	

Отправка файлов в теле запроса

		const xhr = new XMLHttpRequest();
		xhr.open('POST', '/post', false);
		 
		const fileInput = document.querySelector('input[type=file]');
		const file = fileInput.files[0].file;
		const formdata = new FormData();
		formdata.append('file', file);
		xhr.send(formdata);
		// ждём ответа
		 
	

Асинхронные запросы

		const xhr = new XMLHttpRequest();
		xhr.open('GET', '/lib/slides.css', true);
		 
		xhr.onreadystatechange = function() {
		    if (xhr.readyState !== 4) return;
		    console.log(`Ответ от сервера: ${xhr.status} ${xhr.statusText}`);
		    console.log(xhr.responseText);
		}
		xhr.timeout = 5000; // 5 секунд (в миллисекундах)
		xhr.send();
		 
	

Событие readystatechange

		// Значения readyState
		const unsigned short UNSENT = 0; // начальное состояние
		const unsigned short OPENED = 1; // вызван open
		const unsigned short HEADERS_RECEIVED = 2; // получены заголовки
		const unsigned short LOADING = 3; // загружается тело
		const unsigned short DONE = 4; // запрос завершён
		 
	

Другие события

Cookies

HTTP is a stateless protocol

Cookies

Куки (cookies) — небольшой фрагмент данных, отправленный веб-сервером и хранимый на компьютере пользователя. Веб-клиент (обычно веб-браузер) всякий раз при попытке открыть страницу соответствующего сайта пересылает этот фрагмент данных веб-серверу в виде HTTP-запроса

Спецификация cookies

1 Клиент → Сервер

		GET / HTTP/1.1
		Host: example.com
		 
	

2 Клиент ← Сервер

		HTTP/1.1 200 OK
		Set-Cookie: name=value
		Content-Type: text/html
		 
	

3 Клиент → Сервер

		GET / HTTP/1.1
		Host: example.com
		Cookie: name=value
		 
	

4 Клиент ← Сервер

		HTTP/1.1 200 OK
		Set-Cookie: name=value2
		Content-Type: text/html
		 
	

Использование

Set-Cookie

		Set-Cookie: value
		                 [; Expires=date]
		                 [; Max-Age=age]
		                 [; Domain=domain]
		                 [; Path=path]
		                 [; Secure]
		                 [; HttpOnly]
		 
	

Cookie ID

		Set-Cookie: name=v1; domain=.example.com; path=/
		Set-Cookie: name=v2; domain=www.example.com; path=/
		Set-Cookie: name=v3; domain=.example.com; path=/archive
		 
	
		Cookie: name=v1; name=v2; name=v3
		 
	

JavaScript

		document.cookie; /* 'name1=value1' */
		document.cookie = 'name2=value2';
		document.cookie; /* 'name1=value1; name2=value2' */
		 
	

Credentials вместе с XHR

		const xhr = new XMLHttpRequest();
		xhr.open('GET', '/auth', true);
		 
		xhr.withCredentials = true; // отправляем cookies
		 
		xhr.send();
		 
	

Итого: авторизация через куки

Практика!

Полезные ссылки

Всем спасибо!