Web API и работа с DOM Flashcards
Как устроен DOM и как его можно манипулировать?
DOM (Document Object Model - Объектная Модель Документа)
DOM — это программный интерфейс (API) для работы с HTML, XML и SVG документами. Он представляет документ в виде древовидной структуры, где каждый элемент, атрибут и текстовый фрагмент являются узлами (nodes) этого дерева. Браузеры используют DOM для отображения веб-страниц, а JavaScript (и другие языки программирования) может использовать DOM API для динамического изменения структуры, содержимого и стиля документа.
Ключевые концепции DOM:
-
Древовидная структура:
- Документ представляется в виде дерева, где:
- Корневой узел (Document): Вершина дерева, представляющая весь документ.
-
Узлы-элементы (Element nodes): HTML-теги (например,
<html>
,<head>
,<body>
,<div>
,<p>
,<a>
). -
Узлы-атрибуты (Attribute nodes): Атрибуты HTML-тегов (например,
id
,class
,href
,src
). - Текстовые узлы (Text nodes): Текстовое содержимое внутри HTML-тегов.
-
Узлы-комментарии (Comment nodes): HTML-комментарии (
<!-- ... -->
).
- Узлы связаны отношениями “родитель-потомок” (parent-child) и “брат-брат” (sibling).
- Документ представляется в виде дерева, где:
-
Узлы (Nodes):
- Каждый элемент, атрибут, текст и комментарий в DOM является узлом.
- Узлы имеют свойства и методы, которые позволяют получать информацию о них и манипулировать ими.
- Основные типы узлов:
-
Node.ELEMENT_NODE
(1): Узел-элемент (HTML-тег). -
Node.ATTRIBUTE_NODE
(2): Узел-атрибут. -
Node.TEXT_NODE
(3): Текстовый узел. -
Node.COMMENT_NODE
(8): Узел-комментарий. -
Node.DOCUMENT_NODE
(9): Корневой узел документа. -
Node.DOCUMENT_FRAGMENT_NODE
(11): Фрагмент документа (легковесный контейнер для узлов, который не является частью основного дерева DOM).
-
-
Интерфейсы:
- DOM API предоставляет набор интерфейсов для работы с различными типами узлов.
- Основные интерфейсы:
-
Node
: Базовый интерфейс для всех узлов. -
Element
: Интерфейс для узлов-элементов (HTML-тегов). -
Document
: Интерфейс для корневого узла документа. -
Attr
: Интерфейс для узлов-атрибутов. -
Text
: Интерфейс для текстовых узлов. -
Comment
: Интерфейс для узлов-комментариев. -
DocumentFragment
: Интерфейс для фрагментов документа. -
EventTarget
: Интерфейс для объектов, которые могут получать события (например, узлы DOM). -
Event
: Интерфейс для событий.
-
Пример структуры DOM (для простого HTML-документа):
```html
<!DOCTYPE html>
<html>
<head>
<meta></meta>
<title>Пример DOM</title>
</head>
<body>
<h1>Заголовок</h1>
<p>Текст <a>ссылка</a>.</p>
<!-- Комментарий -->
</body>
</html>
**Древовидное представление (упрощённое):**
Document
└── html (lang=”en”)
├── head
│ ├── meta (charset=”UTF-8”)
│ └── title
│ └── “Пример DOM” (Text node)
└── body
├── h1
│ └── “Заголовок” (Text node)
├── p
│ ├── “Текст “ (Text node)
│ ├── a (href=”#”)
│ │ └── “ссылка” (Text node)
│ └── “.” (Text node)
└── <!-- Комментарий --> (Comment node)
~~~
Манипулирование DOM с помощью JavaScript:
JavaScript предоставляет мощные средства для взаимодействия с DOM. Вот основные способы:
-
Выбор элементов (Selecting Elements):
-
document.getElementById(id)
: Возвращает элемент с указаннымid
(самый быстрый способ). -
document.querySelector(selector)
: Возвращает первый элемент, соответствующий указанному CSS-селектору. -
document.querySelectorAll(selector)
: Возвращает все элементы, соответствующие указанному CSS-селектору (в видеNodeList
). -
document.getElementsByClassName(className)
: Возвращает все элементы с указанным классом (в видеHTMLCollection
). -
document.getElementsByTagName(tagName)
: Возвращает все элементы с указанным тегом (в видеHTMLCollection
). -
element.querySelector(selector)
иelement.querySelectorAll(selector)
: Поиск элементов внутри другого элемента.
// Получение элемента по ID
const heading = document.getElementById(‘myHeading’);// Получение первого элемента с классом “paragraph”
const firstParagraph = document.querySelector(‘.paragraph’);// Получение всех элементов с классом “paragraph”
const allParagraphs = document.querySelectorAll(‘.paragraph’);// Получение всех элементов <a> внутри элемента <nav>
const navLinks = document.querySelector(‘nav’).querySelectorAll(‘a’);
```</a> -
-
Навигация по DOM (Traversing the DOM):
-
parentNode
: Родительский узел. -
childNodes
: Дочерние узлы (в видеNodeList
). -
firstChild
: Первый дочерний узел. -
lastChild
: Последний дочерний узел. -
nextSibling
: Следующий “братский” узел (узел с тем же родителем). -
previousSibling
: Предыдущий “братский” узел. -
children
: Дочерние элементы (в видеHTMLCollection
, без текстовых узлов и комментариев). -
firstElementChild
: Первый дочерний элемент. -
lastElementChild
: Последний дочерний элемент. -
nextElementSibling
: Следующий “братский” элемент. -
previousElementSibling
: Предыдущий “братский” элемент.
javascript const paragraph = document.querySelector('p'); const parent = paragraph.parentNode; // Получаем родительский элемент (например, <div>) const firstChild = paragraph.firstChild; // Получаем первый дочерний узел (например, текстовый узел) const nextSibling = paragraph.nextSibling; // Получаем следующий братский узел
-
-
Изменение содержимого (Modifying Content):
-
textContent
: Получение или изменение текстового содержимого элемента (включая дочерние элементы). -
innerHTML
: Получение или изменение HTML-содержимого элемента (включая дочерние элементы и теги). Осторожно: использованиеinnerHTML
может привести к XSS-уязвимостям, если вы вставляете данные, полученные от пользователя. -
innerText
: Получение или изменение видимого текстового содержимого элемента (учитывает стили, скрывающие элементы). Менее производительный, чемtextContent
.
const heading = document.getElementById(‘myHeading’);
heading.textContent = ‘Новый заголовок’; // Изменяем текстовое содержимоеconst paragraph = document.querySelector(‘p’);
paragraph.innerHTML = ‘Новый текст с <strong>выделением</strong>.’; // Изменяем HTML-содержимое
``` -
-
Изменение атрибутов (Modifying Attributes):
-
element.getAttribute(attributeName)
: Получение значения атрибута. -
element.setAttribute(attributeName, value)
: Установка значения атрибута. -
element.removeAttribute(attributeName)
: Удаление атрибута. -
element.hasAttribute(attributeName)
: Проверка наличия атрибута. - Для некоторых атрибутов есть специальные свойства (например,
element.id
,element.className
,element.href
,element.src
).
javascript const link = document.querySelector('a'); const href = link.getAttribute('href'); // Получаем значение атрибута href link.setAttribute('href', 'https://www.example.com'); // Устанавливаем новое значение атрибута href link.removeAttribute('target'); // Удаляем атрибут target
-
-
Изменение стилей (Modifying Styles):
-
element.style.property = value
: Изменение инлайнового стиля элемента (например,element.style.color = 'red'
). -
element.className
: Получение или изменение списка классов элемента (строка). -
element.classList
: Более удобный способ работы с классами:-
element.classList.add(className)
: Добавление класса. -
element.classList.remove(className)
: Удаление класса. -
element.classList.toggle(className)
: Переключение класса (добавление, если его нет, и удаление, если он есть). -
element.classList.contains(className)
: Проверка наличия класса.
-
const button = document.querySelector(‘button’);
button.style.backgroundColor = ‘blue’; // Изменяем инлайновый стиль
button.style.color = ‘white’;button.classList.add(‘active’); // Добавляем класс “active”
button.classList.remove(‘inactive’); // Удаляем класс “inactive”
button.classList.toggle(‘highlight’); // Переключаем класс “highlight”
``` -
-
Создание и добавление элементов (Creating and Appending Elements):
-
document.createElement(tagName)
: Создание нового элемента. -
document.createTextNode(text)
: Создание нового текстового узла. -
element.appendChild(newNode)
: Добавление узлаnewNode
в конец списка дочерних узлов элементаelement
. -
element.insertBefore(newNode, referenceNode)
: Вставка узлаnewNode
перед узломreferenceNode
внутри элементаelement
. -
element.removeChild(childNode)
: Удаление дочернего узлаchildNode
из элементаelement
. -
element.replaceChild(newChild, oldChild)
: Замена дочернего узлаoldChild
на узелnewChild
внутри элементаelement
. -
document.createDocumentFragment()
: Создание фрагмента документа (используется для оптимизации, когда нужно добавить много элементов в DOM).
// Создание нового элемента <p>
const newParagraph = document.createElement(‘p’);
newParagraph.textContent = ‘Это новый абзац.’;// Создание нового элемента <a>
const newLink = document.createElement(‘a’);
newLink.href = ‘https://www.example.com’;
newLink.textContent = ‘Ссылка’;</a>// Добавление абзаца в конец элемента <body>
document.body.appendChild(newParagraph);// Добавление ссылки внутрь абзаца
newParagraph.appendChild(newLink);// Вставка нового элемента <li> перед первым элементом <li> в списке <ul>
const list = document.querySelector(‘ul’);
const newItem = document.createElement(‘li’);
newItem.textContent = ‘Новый элемент списка’;
list.insertBefore(newItem, list.firstElementChild);// Удаление элемента
const elementToRemove = document.getElementById(‘removeMe’);
if (elementToRemove) {
elementToRemove.parentNode.removeChild(elementToRemove);
}
``` -
-
Работа с событиями (Handling Events):
-
element.addEventListener(eventType, listener, options)
: Добавление обработчика события. -
element.removeEventListener(eventType, listener, options)
: Удаление обработчика события. -
event
: Объект события, который передаётся в обработчик. Содержит информацию о событии (тип, целевой элемент, координаты мыши и т.д.). -
event.target
: Элемент, на котором произошло событие. -
event.preventDefault()
: Предотвращение действия по умолчанию (например, переход по ссылке, отправка формы). -
event.stopPropagation()
: Остановка “всплытия” события (предотвращение распространения события на родительские элементы).
const button = document.querySelector(‘button’);// Добавление обработчика события “click”
button.addEventListener(‘click’, function(event) {
console.log(‘Кнопка нажата!’);
console.log(‘Целевой элемент:’, event.target);
event.preventDefault(); // Предотвращаем действие по умолчанию (если это, например, ссылка)
});// Добавление обработчика события “mouseover”
button.addEventListener(‘mouseover’, function() {
button.style.backgroundColor = ‘yellow’;
});// Добавление обработчика события “mouseout”
button.addEventListener(‘mouseout’, function() {
button.style.backgroundColor = ‘’; // Возвращаем исходный цвет
});
``` -
-
Работа с формами:
- Доступ к элементам формы:
javascript const form = document.getElementById("myForm"); const nameInput = form.elements.name; // Доступ по имени элемента const emailInput = form.elements["email"]; // Доступ по имени, как к свойству объекта const submitButton = form.querySelector("button[type=submit]");
- Получение и установка значений:
javascript const name = nameInput.value; // Получаем значение emailInput.value = "test@example.com"; // Устанавливаем значение
- Событие
submit
:
javascript form.addEventListener("submit", (event) => { event.preventDefault(); // Предотвращаем отправку по умолчанию // ... обрабатываем данные формы ... console.log("Форма отправлена (но не на сервер)"); });
- Валидация:
javascript if (nameInput.validity.valueMissing) { // Поле обязательно для заполнения nameInput.setCustomValidity("Пожалуйста, введите ваше имя"); // Устанавливаем сообщение об ошибке } else { nameInput.setCustomValidity(""); // Сбрасываем сообщение об ошибке } nameInput.reportValidity(); // Проверяем и показываем/скрываем сообщения об ошибках
- Доступ к элементам формы:
-
Работа с
DocumentFragment
:
javascript const fragment = document.createDocumentFragment(); for (let i = 0; i < 1000; i++) { const li = document.createElement('li'); li.textContent = `Item ${i}`; fragment.appendChild(li); } document.getElementById('myList').appendChild(fragment); // Добавляем все элементы за один раз
Пример (создание и добавление списка):
```html
<!DOCTYPE html>
<html>
<head>
<title>DOM Example</title>
</head>
<body>
<ul></ul>
<script> const list = document.getElementById('myList'); const items = ['Item 1', 'Item 2', 'Item 3']; for (const itemText of items) { const listItem = document.createElement('li'); listItem.textContent = itemText; list.appendChild(listItem); } </script>
</body>
</html>
~~~
В этом примере JavaScript используется для создания списка <ul>
и добавления в него элементов <li>
.
Это лишь базовые примеры манипулирования DOM. DOM API предоставляет гораздо больше возможностей, включая работу с CSS-анимациями, Mutation Observer (отслеживание изменений в DOM), Shadow DOM и многое другое.
В чем разница между innerHTML, textContent, appendChild?
Давайте подробно разберём различия между innerHTML
, textContent
и appendChild
, а также рассмотрим, когда и какой метод лучше использовать.
1. innerHTML
-
Назначение: Свойство
innerHTML
позволяет получить или установить HTML-содержимое элемента. Это означает, что оно работает не только с текстом, но и с HTML-тегами. - Получение содержимого: Возвращает строку, содержащую весь HTML-код внутри элемента, включая теги, атрибуты и вложенные элементы.
- Установка содержимого: Заменяет всё текущее содержимое элемента новым HTML-кодом, который передаётся в виде строки. Браузер парсит эту строку и создаёт соответствующие DOM-узлы.
-
Пример:```html<div>
<p>Это <strong>первый</strong> абзац.</p>
<p>Это второй абзац.</p>
</div><script> const div = document.getElementById('myDiv'); // Получение HTML-содержимого console.log(div.innerHTML); // Вывод: // <p>Это <strong>первый</strong> абзац.</p> // <p>Это второй абзац.</p> // Установка HTML-содержимого div.innerHTML = '<p>Это <em>новый</em> абзац.</p>'; // Теперь содержимое div: // <div id="myDiv"> // <p>Это <em>новый</em> абзац.</p> // </div> </script>
``` -
Плюсы:
- Удобно для быстрой вставки HTML-разметки.
- Позволяет создавать сложные структуры элементов одной строкой.
-
Минусы:
-
Безопасность (XSS-уязвимости): Если вы вставляете в
innerHTML
данные, полученные от пользователя (например, из формы ввода), то злоумышленник может внедрить вредоносный JavaScript-код (Cross-Site Scripting, XSS). Никогда не используйтеinnerHTML
для вставки ненадёжных данных без предварительной очистки (санации). -
Производительность: При каждом изменении
innerHTML
браузеру приходится удалять все существующие дочерние узлы элемента, парсить новую строку HTML и создавать новые узлы. Это может быть медленно, особенно если элемент содержит много дочерних узлов или если вы часто изменяетеinnerHTML
. -
Потеря обработчиков событий: При изменении
innerHTML
все обработчики событий, привязанные к дочерним элементам, теряются. -
Перезапись всего содержимого:
innerHTML
полностью заменяет содержимое, а не добавляет к нему.
-
Безопасность (XSS-уязвимости): Если вы вставляете в
2. textContent
-
Назначение: Свойство
textContent
позволяет получить или установить текстовое содержимое элемента и его потомков, без HTML-тегов. - Получение содержимого: Возвращает строку, содержащую весь текст внутри элемента, включая текст вложенных элементов, но без самих тегов. Пробелы и переносы строк сохраняются.
-
Установка содержимого: Заменяет всё текущее содержимое элемента новым текстом. Любые символы, которые могут быть интерпретированы как HTML-теги (например,
<
,>
,&
), автоматически экранируются (преобразуются в HTML-сущности:<
,>
,&
). -
Пример:```html<div>
<p>Это <strong>первый</strong> абзац.</p>
<p>Это второй абзац.</p>
</div><script> const div = document.getElementById('myDiv'); // Получение текстового содержимого console.log(div.textContent); // Вывод: // Это первый абзац. // Это второй абзац. // Установка текстового содержимого div.textContent = 'Это <новый> абзац.'; // Теперь содержимое div: // <div id="myDiv">Это <новый> абзац.</div> // (обратите внимание, что < и > были экранированы) </script>
``` -
Плюсы:
- Безопасность: Автоматическое экранирование предотвращает XSS-уязвимости. Безопасно использовать для вставки любых данных, включая данные от пользователя.
-
Производительность: Работает быстрее, чем
innerHTML
, так как не требует парсинга HTML. - Сохранение пробелов: Сохраняет все пробелы и переносы строк.
-
Минусы:
- Нельзя вставить HTML-теги.
- Заменяет всё содержимое, а не добавляет.
3. appendChild()
-
Назначение: Метод
appendChild()
добавляет узел (node) в конец списка дочерних узлов указанного родительского узла. Это не свойство, а метод. -
Аргументы: Принимает один аргумент — узел, который нужно добавить. Это может быть:
- Узел, созданный с помощью
document.createElement()
. - Узел, созданный с помощью
document.createTextNode()
. - Существующий узел, полученный с помощью методов выбора элементов (например,
document.getElementById()
). В этом случае узел будет перемещён из своего текущего положения в новое.
- Узел, созданный с помощью
- Возвращаемое значение: Возвращает добавленный узел.
-
Пример:```html<ul>
<li>Первый элемент</li>
</ul><script> const list = document.getElementById('myList'); // Создание нового элемента <li> const newItem = document.createElement('li'); newItem.textContent = 'Второй элемент'; // Добавление нового элемента в конец списка list.appendChild(newItem); // Создание текстового узла const textNode = document.createTextNode('Третий элемент (текст)'); // Создание элемента <li> и добавление в него текстового узла const newItem2 = document.createElement('li'); newItem2.appendChild(textNode); list.appendChild(newItem2); // Перемещение существующего элемента const firstItem = document.querySelector('li'); // Получаем первый элемент list.appendChild(firstItem); // Перемещаем его в конец списка </script>
``` -
Плюсы:
- Безопасность: Не интерпретирует HTML, поэтому безопасен для использования с любыми данными.
-
Производительность: Работает быстрее, чем
innerHTML
, при добавлении новых элементов, так как не требует парсинга HTML и пересоздания всех дочерних узлов. - Гибкость: Позволяет добавлять любые узлы, а не только текст или HTML-строки.
- Не перезаписывает содержимое: Добавляет элемент в конец, не удаляя существующие.
- Сохраняет обработчики: Обработчики событий, привязанные к перемещаемым элементам, сохраняются.
-
Минусы:
- Требует больше кода для создания сложных структур элементов (по сравнению с
innerHTML
). - Добавляет только в конец списка дочерних элементов (для вставки в другое место используйте
insertBefore()
).
- Требует больше кода для создания сложных структур элементов (по сравнению с
Сравнение:
Характеристика | innerHTML
Как работает MutationObserver?
MutationObserver
— это мощный API в JavaScript, который позволяет асинхронно отслеживать изменения в DOM-дереве. Он предоставляет способ реагировать на добавление, удаление или изменение узлов, изменение атрибутов и изменение текстового содержимого элементов. Это значительно более эффективный и гибкий подход по сравнению со старыми, ныне устаревшими, Mutation Events.
Как работает MutationObserver
:
-
Создание экземпляра:
- Вы создаете новый экземпляр
MutationObserver
с помощью конструктора, передавая ему функцию обратного вызова (callback). Эта функция будет вызываться каждый раз, когда происходят отслеживаемые изменения.
javascript const observer = new MutationObserver(callback);
- Вы создаете новый экземпляр
-
Настройка наблюдения (конфигурация):
- Вы вызываете метод
observe()
у созданного экземпляра, указывая целевой DOM-узел (элемент или документ) и объект конфигурации. Объект конфигурации определяет, какие именно изменения вы хотите отслеживать.
javascript observer.observe(targetNode, config);
-
targetNode
: DOM-узел (обычно элемент), за которым вы хотите наблюдать. Можно наблюдать и за всем документом (document
). -
config
: Объект с настройками. Основные свойства:-
childList
:true
илиfalse
. Отслеживать добавление или удаление дочерних узлов уtargetNode
. -
attributes
:true
илиfalse
. Отслеживать изменения атрибутов уtargetNode
. -
characterData
:true
илиfalse
. Отслеживать изменения текстового содержимогоtargetNode
(или его потомков, еслиsubtree
тожеtrue
). -
subtree
:true
илиfalse
. Отслеживать изменения не только в самомtargetNode
, но и во всех его потомках (включая вложенные). -
attributeOldValue
:true
илиfalse
. Записывать предыдущее значение изменённого атрибута в объектMutationRecord
(подробнее о нём ниже). -
characterDataOldValue
:true
илиfalse
. Записывать предыдущее значение изменённого текстового содержимого. -
attributeFilter
: Массив строк. Список имён атрибутов, изменения которых нужно отслеживать. Если этот параметр указан, тоattributes
должен бытьtrue
, но будут отслеживаться изменения только перечисленных атрибутов.
-
- Вы вызываете метод
-
Функция обратного вызова (Callback):
- Функция, которую вы передаёте в конструктор
MutationObserver
, вызывается асинхронно после того, как произошли изменения в DOM. - Функция обратного вызова получает два аргумента:
-
mutations
: Массив объектовMutationRecord
. Каждый объектMutationRecord
описывает одно произошедшее изменение. -
observer
: Сам экземплярMutationObserver
. Это может быть полезно, если вы используете одну и ту же функцию обратного вызова для нескольких наблюдателей.
-
- Функция, которую вы передаёте в конструктор
-
Объект
MutationRecord
:- Каждый объект
MutationRecord
в массивеmutations
содержит информацию об одном изменении DOM. Основные свойства:-
type
: Строка, тип изменения. Возможные значения:-
"attributes"
: Изменение атрибута. -
"characterData"
: Изменение текстового содержимого. -
"childList"
: Добавление или удаление дочерних узлов.
-
-
target
: DOM-узел, в котором произошло изменение. -
addedNodes
:NodeList
(список узлов), содержащий добавленные узлы (еслиtype
равен"childList"
). -
removedNodes
:NodeList
, содержащий удалённые узлы (еслиtype
равен"childList"
). -
previousSibling
: Предыдущий “братский” узел для добавленного или удалённого узла (еслиtype
равен"childList"
). -
nextSibling
: Следующий “братский” узел для добавленного или удалённого узла (еслиtype
равен"childList"
). -
attributeName
: Имя изменённого атрибута (еслиtype
равен"attributes"
). -
attributeNamespace
: Пространство имён изменённого атрибута (если применимо). -
oldValue
: Предыдущее значение изменённого атрибута или текстового содержимого (еслиattributeOldValue
илиcharacterDataOldValue
были установлены вtrue
в конфигурации).
-
- Каждый объект
-
Остановка наблюдения:
- Чтобы прекратить наблюдение, вызовите метод
disconnect()
у экземпляраMutationObserver
.
javascript observer.disconnect();
- Чтобы прекратить наблюдение, вызовите метод
-
takeRecords()
:- Метод
takeRecords()
возвращает очередь ожидающих записей об изменениях (массив объектовMutationRecord
) и очищает эту очередь. Это полезно, если вам нужно обработать изменения, которые произошли до того, как был вызван ваш callback. Обычно используется редко.
- Метод
Примеры:
-
Отслеживание добавления и удаления дочерних элементов:```javascript
const target = document.getElementById(‘myContainer’);const observer = new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === ‘childList’) {
console.log(‘Добавленные узлы:’, mutation.addedNodes);
console.log(‘Удалённые узлы:’, mutation.removedNodes);
}
}
});const config = { childList: true };
observer.observe(target, config);// Добавляем элемент (вызовет callback)
const newElement = document.createElement(‘div’);
target.appendChild(newElement);// Удаляем элемент (вызовет callback)
target.removeChild(newElement);observer.disconnect(); // Останавливаем наблюдение
``` -
Отслеживание изменений атрибута:```javascript
const target = document.getElementById(‘myElement’);const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === ‘attributes’) {
console.log(Изменён атрибут ${mutation.attributeName}. Старое значение: ${mutation.oldValue}
);
}
}
});const config = { attributes: true, attributeOldValue: true, attributeFilter: [‘data-value’] }; // Отслеживаем только data-value
observer.observe(target, config);// Изменяем атрибут (вызовет callback)
target.setAttribute(‘data-value’, ‘new-value’);
target.setAttribute(‘another-attribute’, ‘some-value’); // Не вызовет callback, т.к. не отслеживаетсяobserver.disconnect();
``` -
Отслеживание изменений текстового содержимого:```javascript
const target = document.getElementById(‘myElement’);const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === ‘characterData’) {
console.log(‘Изменён текст. Старое значение:’, mutation.oldValue);
}
}
});const config = { characterData: true, characterDataOldValue: true, subtree: true }; // Отслеживаем и в потомках
observer.observe(target, config);// Изменяем текст (вызовет callback)
target.textContent = ‘Новый текст’;// Изменяем текст в дочернем элементе (тоже вызовет callback, т.к. subtree: true)
target.querySelector(‘span’).textContent = ‘Изменённый текст внутри span’;observer.disconnect();
``` -
Использование
takeRecords()
:```javascript
const target = document.getElementById(‘myElement’);
const observer = new MutationObserver((mutationsList) => {
console.log(“Callback вызван”);
for (const mutation of mutationsList) {
console.log(mutation.type);
}
});
const config = { childList: true };
observer.observe(target, config);// Добавляем несколько элементов до вызова callback
const div1 = document.createElement(‘div’);
const div2 = document.createElement(‘div’);
target.appendChild(div1);
target.appendChild(div2);// Получаем и обрабатываем ожидающие изменения
const pendingMutations = observer.takeRecords();
console.log(“Ожидающие изменения:”);
for (const mutation of pendingMutations) {
console.log(mutation.type);
}// Callback всё равно будет вызван (асинхронно), но очередь изменений уже будет пуста
```
Ключевые преимущества MutationObserver
перед Mutation Events:
-
Производительность:
MutationObserver
работает асинхронно. Изменения группируются и обрабатываются в одном пакете, что значительно снижает нагрузку на браузер, особенно при большом количестве изменений. Mutation Events были синхронными, что приводило к серьёзным проблемам с производительностью. -
Надёжность: Mutation Events могли вызывать неожиданные побочные эффекты и были подвержены проблемам с порядком выполнения.
MutationObserver
более предсказуем и надёжен. -
Гибкость:
MutationObserver
предоставляет более гибкую конфигурацию, позволяя точно указать, какие изменения нужно отслеживать. -
Стандартизация:
MutationObserver
является современным стандартом, поддерживаемым всеми основными браузерами. Mutation Events устарели и не рекомендуются к использованию.
Когда использовать MutationObserver
:
- Отслеживание динамически добавляемого или удаляемого контента (например, при AJAX-загрузке).
- Реализация “бесконечной прокрутки” (подгрузка контента при прокрутке страницы).
- Создание собственных компонентов пользовательского интерфейса, которые реагируют на изменения в DOM.
- Отслеживание изменений в сторонних библиотеках или фреймворках.
- Реализация undo/redo функциональности.
- Мониторинг изменений в DOM для целей тестирования или отладки.
MutationObserver
— это незаменимый инструмент для создания современных, динамичных и отзывчивых веб-приложений. Он позволяет эффективно реагировать на изменения в DOM, не перегружая браузер.
Что такое Intersection Observer и где его используют?
IntersectionObserver
— это современный веб-API, который предоставляет простой и эффективный способ асинхронно отслеживать, когда элемент (или группа элементов) появляется в области видимости (viewport) браузера или другого элемента-контейнера (root element). Он позволяет реагировать на пересечение целевого элемента с областью видимости, не прибегая к постоянным проверкам положения элемента на странице, что значительно улучшает производительность.
Как работает IntersectionObserver
:
-
Создание экземпляра:
- Вы создаете новый экземпляр
IntersectionObserver
с помощью конструктора, передавая ему функцию обратного вызова (callback) и необязательный объект с настройками (options).
javascript const observer = new IntersectionObserver(callback, options);
- Вы создаете новый экземпляр
-
Настройка наблюдения (options):
-
callback
: Функция, которая будет вызываться каждый раз, когда наблюдаемый элемент пересекает область видимости (или выходит из неё). -
options
: Необязательный объект с настройками. Основные свойства:-
root
: Элемент, который используется в качестве области видимости для проверки пересечения. Еслиnull
(по умолчанию), то используется область видимости браузера (viewport). Это может быть любой предок целевого элемента. -
rootMargin
: Строка, похожая на CSS-свойствоmargin
. Позволяет расширить или сузить границыroot
для целей пересечения. Например,'10px 20px 30px 40px'
(сверху, справа, снизу, слева). Можно использовать проценты. По умолчанию'0px 0px 0px 0px'
. -
threshold
: Число или массив чисел от 0 до 1. Указывает, при какой доле видимости целевого элемента должен срабатывать callback. Например:-
0
: Callback сработает, как только хотя бы один пиксель целевого элемента станет видимым. -
1
: Callback сработает, только когда целевой элемент станет полностью видимым. -
[0, 0.25, 0.5, 0.75, 1]
: Callback будет срабатывать при 0%, 25%, 50%, 75% и 100% видимости. По умолчанию[0]
.
-
-
-
-
Начало наблюдения:
- Вы вызываете метод
observe()
у созданного экземпляра, передавая ему целевой элемент (или элементы), за которым нужно наблюдать.
javascript observer.observe(targetElement);
- Вы вызываете метод
-
Функция обратного вызова (Callback):
- Функция, которую вы передаёте в конструктор
IntersectionObserver
, вызывается асинхронно при каждом пересечении целевого элемента с областью видимости (или при выходе из неё). - Функция обратного вызова получает два аргумента:
-
entries
: Массив объектовIntersectionObserverEntry
. Каждый объектIntersectionObserverEntry
описывает состояние пересечения одного наблюдаемого элемента. -
observer
: Сам экземплярIntersectionObserver
.
-
- Функция, которую вы передаёте в конструктор
-
Объект
IntersectionObserverEntry
:- Каждый объект
IntersectionObserverEntry
в массивеentries
содержит информацию о пересечении одного целевого элемента. Основные свойства:-
boundingClientRect
: ОбъектDOMRect
, описывающий размер и положение целевого элемента относительно окна браузера. -
intersectionRatio
: Число от 0 до 1, указывающее, какая доля целевого элемента видна в области видимости. -
intersectionRect
: ОбъектDOMRect
, описывающий размер и положение пересекающейся части целевого элемента (той части, которая видна). -
isIntersecting
:true
, если целевой элемент пересекает область видимости (хотя бы частично),false
в противном случае. -
rootBounds
: ОбъектDOMRect
, описывающий размер и положениеroot
элемента (или viewport, еслиroot
не указан). -
target
: Сам наблюдаемый DOM-элемент. -
time
: Временная метка (timestamp), когда произошло пересечение.
-
- Каждый объект
-
Остановка наблюдения:
- Чтобы прекратить наблюдение за конкретным элементом, вызовите метод
unobserve()
у экземпляраIntersectionObserver
, передав ему этот элемент.
javascript observer.unobserve(targetElement);
- Чтобы прекратить наблюдение за всеми элементами, вызовите метод
disconnect()
.
javascript observer.disconnect();
- Чтобы прекратить наблюдение за конкретным элементом, вызовите метод
Где используют IntersectionObserver
:
-
Ленивая загрузка изображений (Lazy Loading):
- Загрузка изображений только тогда, когда они попадают в область видимости пользователя. Это значительно ускоряет загрузку страницы и экономит трафик.
const images = document.querySelectorAll(‘img[data-src]’);const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src; // Загружаем изображение
observer.unobserve(entry.target); // Прекращаем наблюдение
}
});
}, {
rootMargin: ‘200px 0px’, // Начинаем загрузку чуть раньше
threshold: 0.1 // Загружаем, когда виден хотя бы 10% изображения
});images.forEach(image => {
observer.observe(image);
});
``` -
Бесконечная прокрутка (Infinite Scroll):
- Подгрузка нового контента, когда пользователь прокручивает страницу до определённого места (например, до конца текущего списка).
const loader = document.getElementById(‘loader’); // Элемент-индикатор загрузкиconst observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
// Загружаем следующую порцию данных
loadMoreData();
}
});observer.observe(loader);
``` -
Отложенное выполнение анимаций:
- Запуск анимаций только тогда, когда элемент появляется в области видимости.
const animatedElements = document.querySelectorAll(‘.animate-on-scroll’);const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add(‘animate’); // Добавляем класс, запускающий анимацию
observer.unobserve(entry.target);
}
});
});animatedElements.forEach(element => {
observer.observe(element);
});
``` -
Отслеживание видимости рекламы:
- Определение, видит ли пользователь рекламный блок, для более точного подсчёта показов.
-
Определение, читает ли пользователь статью:
- Можно отслеживать, находится ли заголовок следующего раздела статьи в области видимости, чтобы определить, дочитал ли пользователь до этого места.
-
Прилипчивые (sticky) элементы:
- Реализация “прилипчивых” заголовков или панелей навигации, которые остаются видимыми при прокрутке. Хотя это можно сделать и с помощью CSS (
position: sticky
),IntersectionObserver
может быть полезен в более сложных случаях.
- Реализация “прилипчивых” заголовков или панелей навигации, которые остаются видимыми при прокрутке. Хотя это можно сделать и с помощью CSS (
-
Воспроизведение/пауза видео:
- Автоматическое воспроизведение видео, когда оно попадает в область видимости, и приостановка, когда оно выходит из неё.
-
Аналитика:
- Отслеживание, какие секции страницы пользователь реально просматривает.
Преимущества IntersectionObserver
:
-
Производительность: Гораздо более эффективен, чем постоянные проверки положения элементов с помощью
getBoundingClientRect()
или обработчиков событийscroll
иresize
. Браузер оптимизирует работуIntersectionObserver
. - Асинхронность: Не блокирует основной поток выполнения JavaScript.
- Простота использования: Простой и понятный API.
- Гибкость: Можно настраивать область видимости, пороги срабатывания и отступы.
- Поддержка браузерами: Хорошо поддерживается современными браузерами. Для старых браузеров существуют полифилы.
Пример (общий):
```html
<!DOCTYPE html>
<html>
<head>
<title>Intersection Observer Example</title>
<style>
.box { width: 200px; height: 200px; background-color: lightblue; margin: 20px; border: 1px solid black; } .intersecting { background-color: lightgreen; }</style>
</head>
<body>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<script> const boxes = document.querySelectorAll('.box'); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('intersecting'); console.log("Element is intersecting:", entry.target); } else { entry.target.classList.remove('intersecting'); console.log("Element is NOT intersecting:", entry.target); } }); }, { threshold: 0.5 // Срабатывать, когда видна половина элемента }); boxes.forEach(box => { observer.observe(box); }); </script>
</body>
</html>
~~~
В этом примере при прокрутке страницы, когда половина элемента .box
становится видимой, к нему добавляется класс intersecting
, и фон меняется на светло-зелёный. В консоль выводятся сообщения о пересечении.
IntersectionObserver
— это очень полезный инструмент для оптимизации производительности и создания интерактивных веб-страниц. Он позволяет легко и эффективно отслеживать видимость элементов и реагировать на неё.
Как работают localStorage, sessionStorage, cookies?
localStorage
, sessionStorage
и cookies — это механизмы хранения данных на стороне клиента (в браузере), которые позволяют веб-сайтам сохранять информацию между сеансами просмотра или в рамках одного сеанса. У каждого из них свои особенности, ограничения и сценарии использования.
1. Cookies (Куки)
- Назначение: Cookies — это небольшие текстовые файлы, которые веб-серверы могут хранить на компьютере пользователя через браузер. Они используются для хранения различной информации, такой как настройки пользователя, данные аутентификации, идентификаторы сеансов, содержимое корзины покупок и т.д.
-
Механизм работы:
-
Установка: Веб-сервер отправляет cookie браузеру в HTTP-заголовке
Set-Cookie
. Браузер сохраняет cookie и связывает его с доменом, который его установил. -
Чтение: При каждом последующем запросе к тому же домену браузер автоматически отправляет cookie обратно серверу в HTTP-заголовке
Cookie
. -
Удаление: Сервер может удалить cookie, отправив новый
Set-Cookie
с датой истечения срока действия в прошлом. Также cookie может быть удален, если истек его срок действия, заданный при установке.
-
Установка: Веб-сервер отправляет cookie браузеру в HTTP-заголовке
-
Свойства (атрибуты) cookie:
-
name=value
: Обязательная пара “имя=значение”. -
expires
: Дата истечения срока действия cookie. Если не указана, cookie считается сессионным и удаляется при закрытии браузера. -
max-age
: Время жизни cookie в секундах. Более современная альтернативаexpires
. -
domain
: Домен, для которого действует cookie. Если не указан, cookie доступен только для домена, который его установил (и его поддоменов). -
path
: Путь на сервере, для которого действует cookie. Если не указан, cookie доступен для всего домена. -
secure
: Если установлен, cookie передаётся только по HTTPS-соединению. -
HttpOnly
: Если установлен, cookie недоступен для JavaScript (защита от XSS-атак). -
SameSite
: Контролирует, как cookie отправляется при кросс-сайтовых запросах (защита от CSRF-атак). Возможные значения:-
Strict
: Cookie отправляется только при запросах с того же сайта. -
Lax
: Cookie отправляется при запросах с того же сайта и при навигационных запросах с других сайтов (например, при переходе по ссылке). -
None
: Cookie отправляется при любых запросах (требуетсяSecure
).
-
-
-
Доступ из JavaScript:
- Чтение:
document.cookie
возвращает строку, содержащую все cookie для текущего домена, разделённые точкой с запятой. - Запись:
document.cookie = "name=value; expires=...; path=...; domain=...; secure; HttpOnly; SameSite=..."
. - Удаление:
document.cookie = "name=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"
.
- Чтение:
-
Ограничения:
- Максимальный размер одного cookie: обычно около 4 КБ (4096 байт).
- Общее количество cookie для одного домена: ограничено браузером (обычно от 20 до 50).
- Браузеры могут блокировать сторонние cookie (third-party cookies) для повышения конфиденциальности.
-
Пример (JavaScript):```javascript
// Установка cookie
document.cookie = “username=JohnDoe; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/; SameSite=Lax”;// Чтение cookie
const cookies = document.cookie;
console.log(cookies); // Вывод: username=JohnDoe// Удаление cookie
document.cookie = “username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;”;
``` -
Пример (HTTP-заголовки):```
// Сервер отправляет cookie:
Set-Cookie: sessionid=12345; HttpOnly; Secure; SameSite=Strict
Set-Cookie: username=Alice; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/// Браузер отправляет cookie:
Cookie: sessionid=12345; username=Alice
```
2. localStorage
-
Назначение:
localStorage
предоставляет простой механизм хранения данных в виде пар “ключ-значение” на стороне клиента. Данные сохраняются бессрочно (пока пользователь явно не очистит хранилище или не удалит данные через код). Данные доступны только для того домена, который их сохранил. -
Механизм работы:
- Данные хранятся в виде строк.
- Доступ к данным осуществляется через объект
localStorage
.
-
Методы:
-
localStorage.setItem(key, value)
: Сохраняет значениеvalue
под ключомkey
. -
localStorage.getItem(key)
: Возвращает значение, сохранённое под ключомkey
, илиnull
, если такого ключа нет. -
localStorage.removeItem(key)
: Удаляет значение, сохранённое под ключомkey
. -
localStorage.clear()
: Удаляет все данные изlocalStorage
для текущего домена. -
localStorage.key(index)
: Возвращает ключ элемента по его индексу (порядковому номеру).
-
-
Ограничения:
- Максимальный размер: обычно около 5-10 МБ (зависит от браузера).
- Данные хранятся в виде строк. Для хранения объектов их нужно сериализовать в JSON (с помощью
JSON.stringify()
) и десериализовать при чтении (с помощьюJSON.parse()
). - Данные доступны только для того же домена, протокола и порта.
- Данные доступны только через JavaScript (нет атрибута
HttpOnly
).
-
Пример:```javascript
// Сохранение данных
localStorage.setItem(‘username’, ‘JohnDoe’);
localStorage.setItem(‘theme’, ‘dark’);// Чтение данных
const username = localStorage.getItem(‘username’);
const theme = localStorage.getItem(‘theme’);
console.log(username, theme); // Вывод: JohnDoe dark// Удаление данных
localStorage.removeItem(‘theme’);// Очистка всего хранилища
localStorage.clear();// Сохранение объекта
const user = { name: ‘Alice’, age: 30 };
localStorage.setItem(‘user’, JSON.stringify(user));// Чтение объекта
const storedUser = JSON.parse(localStorage.getItem(‘user’));
console.log(storedUser); // Вывод: { name: ‘Alice’, age: 30 }
```
3. sessionStorage
-
Назначение:
sessionStorage
похож наlocalStorage
, но данные хранятся только в течение одной сессии. Сессия заканчивается, когда пользователь закрывает вкладку или окно браузера. -
Механизм работы:
- Полностью аналогичен
localStorage
.
- Полностью аналогичен
-
Методы:
sessionStorage.setItem(key, value)
sessionStorage.getItem(key)
sessionStorage.removeItem(key)
sessionStorage.clear()
sessionStorage.key(index)
-
Ограничения:
- Те же, что и у
localStorage
.
- Те же, что и у
-
Пример:```javascript
// Сохранение данных
sessionStorage.setItem(‘cartId’, ‘12345’);// Чтение данных
const cartId = sessionStorage.getItem(‘cartId’);// Закрываем вкладку/окно - данные удаляются
```
Сравнение:
Характеристика | Cookies
Как работает Fetch API и чем он лучше XMLHttpRequest?
Fetch API — это современный интерфейс JavaScript для выполнения сетевых запросов. Он предоставляет более гибкий и мощный набор функций по сравнению с XMLHttpRequest (XHR).
Как работает Fetch API?
Fetch API основан на промисах (Promises), что упрощает обработку асинхронных операций. Основная функция — fetch()
, которая принимает URL-адрес ресурса в качестве аргумента и возвращает промис. Этот промис разрешается объектом Response
, представляющим ответ на запрос.
```javascript
fetch(‘https://api.example.com/data’)
.then(response => {
// Обработка ответа
if (response.ok) {
return response.json(); // Например, преобразование ответа в JSON
} else {
throw new Error(‘Сетевая ошибка’);
}
})
.then(data => {
// Обработка данных
console.log(data);
})
.catch(error => {
// Обработка ошибок
console.error(‘Ошибка:’, error);
});
~~~
Ключевые особенности Fetch API:
- Промисы: Fetch API использует промисы, что делает асинхронный код более читаемым и управляемым, чем обратные вызовы, часто используемые с XHR.
- Объект Response: Предоставляет методы для обработки тела ответа в различных форматах (JSON, текст, Blob, ArrayBuffer, FormData).
- Объект Request: Позволяет создавать и настраивать запросы с указанием метода (GET, POST, PUT, DELETE и т. д.), заголовков, тела запроса и других параметров.
- Потоки (Streams): Fetch API поддерживает потоковую обработку данных, что позволяет работать с большими объемами данных, не загружая их полностью в память.
-
Кеширование: Управление кешированием запросов с помощью опций кеша (
cache
). -
Перенаправления: Управление перенаправлениями с помощью опции
redirect
. -
Аборты запросов: Возможность отмены запроса с помощью
AbortController
. -
Credentials: Управление отправкой учетных данных (куки, заголовки авторизации) с помощью опции
credentials
.
Преимущества Fetch API перед XMLHttpRequest:
- Более чистый и современный синтаксис: Fetch API использует промисы, что делает код более читаемым и простым для понимания, особенно при выполнении нескольких последовательных запросов.
-
Более гибкая настройка запросов: Fetch API предоставляет объект
Request
, который позволяет более детально настраивать запросы. - Потоковая обработка: Поддержка потоков позволяет эффективно работать с большими данными.
-
Лучшая обработка ошибок: Fetch API обрабатывает сетевые ошибки (например, 404, 500) как отклоненные промисы, что упрощает обработку ошибок. В XHR для этого нужно проверять
status
иreadyState
. -
Встроенная поддержка JSON: В Fetch API есть удобный метод
response.json()
, который автоматически парсит JSON-ответ. В XHR для этого нужно использоватьJSON.parse()
. - Поддержка в современных браузерах: Fetch API поддерживается всеми современными браузерами. Для старых браузеров можно использовать полифилы.
Недостатки Fetch API:
-
Нет встроенной поддержки прогресса загрузки/выгрузки: Для отслеживания прогресса нужно использовать Readable Streams или другие подходы. В XHR есть события
progress
. -
Не поддерживает таймауты из коробки: Для реализации таймаутов нужно использовать
AbortController
или другие техники. В XHR есть свойствоtimeout
. - Обработка ошибок, связанных с сетью, происходит на уровне
catch
, а ошибки, связанные с HTTP-статусами (4xx, 5xx), нужно обрабатывать, проверяяresponse.ok
илиresponse.status
.
В итоге:
Fetch API — это мощный и современный инструмент для выполнения сетевых запросов в JavaScript. Он предоставляет более удобный и гибкий интерфейс по сравнению с XMLHttpRequest. Несмотря на некоторые недостатки, Fetch API является предпочтительным выбором для большинства задач, связанных с сетевыми запросами.
Что такое CORS и как он работает?
CORS (Cross-Origin Resource Sharing — «совместное использование ресурсов между разными источниками») — это механизм безопасности браузера, который ограничивает веб-страницам доступ к ресурсам с другого домена (источника). Он является частью политики одинакового источника (Same-Origin Policy, SOP), которая предотвращает выполнение вредоносных скриптов с одного сайта на другом.
Что такое “источник”?
Источник (origin) определяется комбинацией трёх составляющих:
-
Протокол:
http
илиhttps
. -
Домен:
example.com
,www.example.com
,api.example.com
. -
Порт:
:80
,:443
,:8080
(если порт не указан явно, используются порты по умолчанию: 80 для HTTP и 443 для HTTPS).
Два URL считаются имеющими одинаковый источник, если все три компонента совпадают.
Примеры:
-
http://example.com/page1
иhttp://example.com/page2
— одинаковый источник. -
http://example.com
иhttps://example.com
— разные источники (разные протоколы). -
http://example.com
иhttp://api.example.com
— разные источники (разные домены). -
http://example.com
иhttp://example.com:8080
— разные источники (разные порты).
Как работает CORS?
Когда веб-страница (например, https://www.example.com
) пытается сделать запрос к ресурсу с другого источника (например, https://api.anotherexample.com/data
), браузер выполняет следующие действия:
-
Preflight запрос (предварительный запрос) (не всегда):
- Для “непростых” запросов (запросов, которые могут изменить данные на сервере, например,
POST
,PUT
,DELETE
, или запросов с нестандартными заголовками) браузер сначала отправляет предварительный запросOPTIONS
к целевому ресурсу. - Этот запрос
OPTIONS
содержит заголовки, указывающие, какой метод (Access-Control-Request-Method
) и какие заголовки (Access-Control-Request-Headers
) будут использоваться в фактическом запросе. - Сервер должен ответить на этот
OPTIONS
запрос с соответствующими CORS-заголовками (см. ниже), разрешающими или запрещающими запрос. - Если сервер не разрешает запрос (нет нужных заголовков или они не соответствуют требованиям), браузер блокирует фактический запрос, и в консоли появляется ошибка CORS.
- Если сервер разрешает запрос, браузер переходит к шагу 2.
- Для “непростых” запросов (запросов, которые могут изменить данные на сервере, например,
-
Фактический запрос:
- Для “простых” запросов (обычно
GET
,HEAD
,POST
сContent-Type: application/x-www-form-urlencoded
,multipart/form-data
,text/plain
) или если preflight запрос был успешен, браузер отправляет фактический запрос к целевому ресурсу. - Браузер автоматически добавляет к запросу заголовок
Origin
, указывающий источник запроса (например,Origin: https://www.example.com
).
- Для “простых” запросов (обычно
-
Ответ сервера:
- Сервер обрабатывает запрос и отправляет ответ.
- Чтобы разрешить кросс-доменный запрос, сервер должен включить в ответ специальные CORS-заголовки.
Ключевые CORS-заголовки (ответ сервера):
-
Access-Control-Allow-Origin
:- Это обязательный заголовок для разрешения кросс-доменных запросов.
- Он указывает, каким источникам разрешен доступ к ресурсу.
- Значения:
-
*
: Разрешает доступ любому источнику (не рекомендуется для запросов с учетными данными). -
<origin>
: Разрешает доступ только указанному источнику (например,https://www.example.com
). -
null
: Используется для локальных файлов (file://), не рекомендуется.
-
-
Access-Control-Allow-Methods
: (Используется в ответе на preflight запросOPTIONS
)- Указывает, какие HTTP-методы разрешены для кросс-доменного запроса (например,
GET, POST, PUT, DELETE
). - Пример:
Access-Control-Allow-Methods: GET, POST, OPTIONS
.
- Указывает, какие HTTP-методы разрешены для кросс-доменного запроса (например,
-
Access-Control-Allow-Headers
: (Используется в ответе на preflight запросOPTIONS
)- Указывает, какие заголовки разрешено использовать в фактическом запросе.
- Пример:
Access-Control-Allow-Headers: Content-Type, Authorization
.
-
Access-Control-Allow-Credentials
:- Указывает, разрешено ли браузеру отправлять учетные данные (куки, HTTP-аутентификацию, клиентские TLS/SSL-сертификаты) вместе с кросс-доменным запросом.
- Значения:
-
true
: Разрешено. -
false
(или отсутствие заголовка): Запрещено.
-
- Если этот заголовок установлен в
true
, тоAccess-Control-Allow-Origin
не может иметь значение*
, а должен явно указывать разрешенный источник.
-
Access-Control-Expose-Headers
:- По умолчанию, JavaScript-код на странице может получить доступ только к ограниченному набору “простых” заголовков ответа (например,
Cache-Control
,Content-Language
,Content-Type
,Expires
,Last-Modified
,Pragma
). - Этот заголовок позволяет серверу указать, какие дополнительные заголовки ответа должны быть доступны JavaScript-коду.
- Пример:
Access-Control-Expose-Headers: X-Custom-Header, X-Another-Header
.
- По умолчанию, JavaScript-код на странице может получить доступ только к ограниченному набору “простых” заголовков ответа (например,
-
Access-Control-Max-Age
: (Используется в ответе на preflight запросOPTIONS
)- Указывает, как долго (в секундах) браузер может кешировать результаты preflight запроса. Это позволяет избежать повторных preflight запросов для একই URL в течение указанного времени.
- Пример:
Access-Control-Max-Age: 3600
(кешировать на 1 час).
Пример (с preflight запросом):
-
Браузер (https://www.example.com):
OPTIONS /data HTTP/1.1 Origin: https://www.example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: Content-Type, Authorization
-
Сервер (https://api.anotherexample.com):
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://www.example.com Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 86400
-
Браузер (https://www.example.com):
```
POST /data HTTP/1.1
Origin: https://www.example.com
Content-Type: application/json
Authorization: Bearer mytoken{ “message”: “Hello” }
``` -
Сервер (https://api.anotherexample.com):
```
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.example.com
Content-Type: application/json{ “response”: “Data received” }
```
Важные моменты:
- CORS — это механизм браузера. Сервер не может “обойти” CORS. Он может только сообщить браузеру, разрешен ли кросс-доменный запрос или нет.
- Если сервер не отправляет правильные CORS-заголовки, браузер заблокирует запрос, и в консоли появится ошибка.
- CORS не защищает сервер от нежелательных запросов. Он защищает пользователей от выполнения вредоносных скриптов, которые могут получить доступ к данным с другого сайта без их ведома.
- Для простых GET запросов, если сервер не присылает заголовок
Access-Control-Allow-Origin
, то данные сервером всё равно будут отправлены, но браузер не даст к ним доступ JavaScript коду.
В заключение: CORS — это важный механизм безопасности, который помогает защитить пользователей от межсайтовых атак. Понимание того, как работает CORS, необходимо для разработки веб-приложений, которые взаимодействуют с ресурсами на разных доменах.
Как работают Service Workers и зачем они нужны?
Service Workers — это скрипты, которые браузер запускает в фоновом режиме, отдельно от веб-страницы. Они предоставляют возможности, которые раньше были доступны только нативным приложениям, такие как:
- Работа в офлайн-режиме: Кеширование ресурсов и обработка запросов, когда нет подключения к интернету.
- Фоновая синхронизация данных: Обновление данных в фоновом режиме, даже если приложение неактивно.
- Push-уведомления: Отображение уведомлений пользователю, даже если браузер закрыт (с разрешения пользователя).
- Перехват сетевых запросов: Полный контроль над сетевыми запросами, что позволяет реализовать сложные стратегии кеширования, проксирование и многое другое.
Как работают Service Workers:
-
Регистрация:
- Веб-страница регистрирует Service Worker с помощью JavaScript API (
navigator.serviceWorker.register()
). - При регистрации указывается путь к JavaScript-файлу Service Worker (например,
/sw.js
). - Браузер загружает этот файл и запускает его в отдельном контексте (Service Worker context).
- Веб-страница регистрирует Service Worker с помощью JavaScript API (
-
Установка (Install):
- После успешной регистрации Service Worker переходит в состояние установки.
- В обработчике события
install
(внутри файлаsw.js
) обычно выполняется кеширование статических ресурсов (HTML, CSS, JavaScript, изображения). - Если установка прошла успешно, Service Worker переходит в состояние ожидания (waiting).
- Если во время установки произошла ошибка (например, не удалось скачать ресурсы), Service Worker отбрасывается.
-
Активация (Activate):
- Service Worker активируется, когда:
- Нет активного Service Worker для данного scope (области действия).
- Произошло обновление файла Service Worker.
- Вызван метод
clients.claim()
(позволяет новому Service Worker немедленно взять под контроль все страницы в своем scope).
- В обработчике события
activate
обычно удаляются старые кеши, которые больше не нужны. - После активации Service Worker начинает контролировать все страницы в своем scope.
- Service Worker активируется, когда:
-
Жизненный цикл:
- Service Worker может находиться в одном из следующих состояний:
- Parsed: Service Worker был разобран браузером.
- Installing: Service Worker устанавливается.
- Installed/Waiting: Service Worker установлен, но ожидает, пока не освободится текущий активный Service Worker.
- Activating: Service Worker активируется.
- Activated: Service Worker активен и контролирует страницы в своем scope.
- Redundant: Service Worker был заменен новым Service Worker или отброшен из-за ошибки.
- Service Worker может находиться в одном из следующих состояний:
-
Обработка событий:
- Service Worker работает на основе событий. Основные события:
-
install
: Срабатывает при установке Service Worker. -
activate
: Срабатывает при активации Service Worker. -
fetch
: Срабатывает при каждом сетевом запросе из контролируемых страниц. Позволяет перехватывать запросы, изменять их, возвращать ответы из кеша или выполнять другие действия. -
push
: Срабатывает при получении push-уведомления от сервера. -
sync
: Срабатывает при фоновой синхронизации данных. -
message
: Позволяет обмениваться сообщениями между Service Worker и веб-страницей.
-
- Service Worker работает на основе событий. Основные события:
Пример (простое кеширование):
```javascript:sw.js
const CACHE_NAME = ‘my-site-cache-v1’;
const urlsToCache = [
‘/’,
‘/index.html’,
‘/style.css’,
‘/script.js’,
‘/image.png’
];
self.addEventListener(‘install’, (event) => {
// Кеширование ресурсов при установке
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log(‘Opened cache’);
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener(‘activate’, (event) => {
// Удаление старых кешей при активации
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log(‘Deleting old cache:’, cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener(‘fetch’, (event) => {
// Обработка сетевых запросов
event.respondWith(
caches.match(event.request)
.then((response) => {
// Возвращаем ответ из кеша, если он есть
if (response) {
return response;
}
// Если ответа в кеше нет, делаем запрос к сети return fetch(event.request).then( (response) => { // Клонируем ответ, так как response - это поток, и его можно прочитать только один раз const responseToCache = response.clone(); caches.open(CACHE_NAME) .then((cache) => { cache.put(event.request, responseToCache); }); return response; } ); }) ); }); ~~~
Регистрация (на веб-странице):
```javascript
if (‘serviceWorker’ in navigator) {
navigator.serviceWorker.register(‘/sw.js’)
.then((registration) => {
console.log(‘Service Worker registered with scope:’, registration.scope);
})
.catch((error) => {
console.error(‘Service Worker registration failed:’, error);
});
}
~~~
Зачем нужны Service Workers:
- Улучшение производительности: Кеширование ресурсов позволяет загружать страницы быстрее, особенно при медленном или нестабильном соединении.
- Офлайн-доступ: Пользователи могут работать с приложением даже без подключения к интернету.
- Улучшение пользовательского опыта: Push-уведомления и фоновая синхронизация делают приложение более отзывчивым и удобным.
- Снижение нагрузки на сервер: Кеширование уменьшает количество запросов к серверу.
- Создание Progressive Web Apps (PWA): Service Workers являются ключевым компонентом PWA, которые сочетают в себе лучшие черты веб-сайтов и нативных приложений.
Важные моменты:
- HTTPS: Service Workers работают только по HTTPS (кроме localhost, что удобно для разработки). Это необходимо для безопасности.
-
Scope (область действия): Service Worker контролирует только те страницы, которые находятся в его scope. Scope определяется расположением файла Service Worker. Например, если файл
sw.js
находится в корне сайта (/sw.js
), то Service Worker будет контролировать все страницы сайта. Если файл находится в папке/js/sw.js
, то Service Worker будет контролировать только страницы, URL которых начинается с/js/
. -
Обновление Service Worker: Браузер автоматически проверяет наличие обновлений файла Service Worker. Если файл изменился, браузер устанавливает новую версию Service Worker в фоновом режиме. Новый Service Worker активируется, когда все вкладки, контролируемые старым Service Worker, будут закрыты (или будет вызван
clients.claim()
). - DevTools: В инструментах разработчика браузера (DevTools) есть специальная вкладка (Application > Service Workers) для отладки Service Workers.
- Безопасность: Service Workers имеют большой доступ к сетевым запросам, поэтому важно тщательно проверять код Service Worker на наличие уязвимостей.
Service Workers — это мощный инструмент, который открывает новые возможности для веб-разработки. Они позволяют создавать быстрые, надежные и удобные веб-приложения, которые работают даже в офлайн-режиме.
В чем разница между WebSockets, SSE и Fetch API?
WebSockets, Server-Sent Events (SSE) и Fetch API — это три разные технологии для обмена данными между клиентом (обычно браузером) и сервером. У каждой из них свои особенности, преимущества и недостатки, и выбор подходящей технологии зависит от конкретных требований приложения.
1. Fetch API:
- Описание: Fetch API — это современный интерфейс JavaScript для выполнения однонаправленных HTTP-запросов. Он основан на промисах и предоставляет более гибкий и мощный набор функций по сравнению с XMLHttpRequest (XHR). Fetch API предназначен для выполнения классических запросов “запрос-ответ”.
- Направление: Однонаправленный (клиент отправляет запрос, сервер отправляет ответ).
- Протокол: HTTP или HTTPS.
- Тип соединения: Кратковременное (соединение закрывается после получения ответа).
-
Сценарии использования:
- Получение данных с сервера (например, JSON, XML, HTML).
- Отправка данных на сервер (например, формы, файлы).
- Взаимодействие с REST API.
- Загрузка ресурсов (изображений, скриптов, стилей).
-
Преимущества:
- Простой и понятный API.
- Поддержка промисов.
- Гибкая настройка запросов (заголовки, методы, тело запроса).
- Поддержка потоковой обработки данных (но не для получения обновлений в реальном времени, а для поэтапной загрузки больших ответов).
- Широкая поддержка в браузерах.
-
Недостатки:
- Не подходит для постоянного двустороннего обмена данными.
- Не поддерживает получение данных от сервера без явного запроса от клиента (нет “push” уведомлений от сервера).
- Каждый запрос требует нового HTTP-соединения.
2. WebSockets:
- Описание: WebSockets — это технология, которая обеспечивает полнодуплексное (двустороннее) постоянное соединение между клиентом и сервером. После установки соединения клиент и сервер могут обмениваться данными в реальном времени в любом направлении без необходимости повторных запросов.
- Направление: Двунаправленный.
-
Протокол:
ws
(незащищенный) илиwss
(защищенный, аналогично HTTPS). - Тип соединения: Постоянное.
-
Сценарии использования:
- Чаты и мессенджеры.
- Онлайн-игры.
- Торговые платформы (биржи, котировки).
- Приложения, требующие обмена данными в реальном времени.
- Совместное редактирование документов.
-
Преимущества:
- Двусторонний обмен данными в реальном времени.
- Низкая задержка (latency).
- Эффективное использование ресурсов (одно соединение для множества сообщений).
-
Недостатки:
- Более сложный API по сравнению с Fetch API.
- Требуется поддержка WebSockets на сервере.
- Сложнее масштабировать, чем HTTP-соединения.
- Сложнее обрабатывать разрывы соединения и повторные подключения.
- Не поддерживается некоторыми старыми браузерами (но есть полифилы).
3. Server-Sent Events (SSE):
- Описание: Server-Sent Events (SSE) — это технология, которая позволяет серверу отправлять данные клиенту по однонаправленному постоянному HTTP-соединению. Клиент подписывается на поток событий от сервера и получает обновления по мере их поступления.
- Направление: Однонаправленный (сервер отправляет данные клиенту).
- Протокол: HTTP или HTTPS.
- Тип соединения: Постоянное.
-
Сценарии использования:
- Новостные ленты.
- Обновления статуса в социальных сетях.
- Уведомления (например, о новых письмах).
- Мониторинг систем.
- Любые приложения, где сервер должен отправлять данные клиенту без явного запроса от клиента.
-
Преимущества:
- Проще в реализации, чем WebSockets (особенно на стороне сервера).
- Использует стандартный протокол HTTP, что упрощает работу с прокси и брандмауэрами.
- Автоматическое переподключение при разрыве соединения (в большинстве браузеров).
- Поддержка в большинстве современных браузеров (кроме Internet Explorer, но есть полифилы).
-
Недостатки:
- Только однонаправленная связь (от сервера к клиенту).
- Менее эффективен, чем WebSockets, для двустороннего обмена данными.
- Ограничение на количество одновременных соединений (обычно 6) в браузере к одному домену (это ограничение HTTP/1.1, а не SSE).
- Текстовый формат данных (обычно используется
text/event-stream
). Для передачи бинарных данных требуется кодирование (например, Base64).
Сравнительная таблица:
Выбор технологии:
- Fetch API: Если вам нужно просто получить или отправить данные по HTTP, и вам не нужен постоянный двусторонний обмен данными в реальном времени.
- WebSockets: Если вам нужен двусторонний обмен данными в реальном времени с минимальной задержкой.
- SSE: Если вам нужно, чтобы сервер отправлял данные клиенту в реальном времени, и вам не нужен двусторонний обмен данными. SSE проще в реализации, чем WebSockets, и хорошо подходит для сценариев, где сервер является основным источником обновлений.
Важно отметить, что эти технологии не являются взаимоисключающими. Например, вы можете использовать Fetch API для начальной загрузки данных, а затем использовать WebSockets или SSE для получения обновлений в реальном времени. Или вы можете использовать Fetch API для отправки данных на сервер, а SSE — для получения уведомлений о результатах обработки.
Характеристика | Fetch API | WebSockets | Server-Sent Events (SSE) |
| ——————— | —————————————– | —————————————– | —————————————– |
| Направление | Однонаправленный (клиент -> сервер) | Двунаправленный | Однонаправленный (сервер -> клиент) |
| Протокол | HTTP/HTTPS | ws/wss | HTTP/HTTPS |
| Тип соединения | Кратковременное | Постоянное | Постоянное |
| Обмен данными | Запрос-ответ | Сообщения в реальном времени | Поток событий |
| Сложность реализации | Простая | Более сложная | Простая (особенно на стороне сервера) |
| Поддержка браузерами | Широкая | Хорошая (есть полифилы) | Хорошая (кроме IE, есть полифилы) |
| Переподключение | Нет | Требуется реализация на стороне клиента/сервера | Автоматическое (в большинстве браузеров) |
| Сценарии | Получение/отправка данных, REST API | Чаты, игры, биржи, real-time приложения | Новостные ленты, уведомления, мониторинг |