Web API и работа с DOM Flashcards

1
Q

Как устроен DOM и как его можно манипулировать?

A

DOM (Document Object Model - Объектная Модель Документа)

DOM — это программный интерфейс (API) для работы с HTML, XML и SVG документами. Он представляет документ в виде древовидной структуры, где каждый элемент, атрибут и текстовый фрагмент являются узлами (nodes) этого дерева. Браузеры используют DOM для отображения веб-страниц, а JavaScript (и другие языки программирования) может использовать DOM API для динамического изменения структуры, содержимого и стиля документа.

Ключевые концепции DOM:

  1. Древовидная структура:
    • Документ представляется в виде дерева, где:
      • Корневой узел (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).
  2. Узлы (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).
  3. Интерфейсы:
    • 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. Вот основные способы:

  1. Выбор элементов (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): Поиск элементов внутри другого элемента.
    ```javascript
    // Получение элемента по 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>
  2. Навигация по 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; // Получаем следующий братский узел
  3. Изменение содержимого (Modifying Content):
    • textContent: Получение или изменение текстового содержимого элемента (включая дочерние элементы).
    • innerHTML: Получение или изменение HTML-содержимого элемента (включая дочерние элементы и теги). Осторожно: использование innerHTML может привести к XSS-уязвимостям, если вы вставляете данные, полученные от пользователя.
    • innerText: Получение или изменение видимого текстового содержимого элемента (учитывает стили, скрывающие элементы). Менее производительный, чем textContent.
    ```javascript
    const heading = document.getElementById(‘myHeading’);
    heading.textContent = ‘Новый заголовок’; // Изменяем текстовое содержимоеconst paragraph = document.querySelector(‘p’);
    paragraph.innerHTML = ‘Новый текст с <strong>выделением</strong>.’; // Изменяем HTML-содержимое
    ```
  4. Изменение атрибутов (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
  5. Изменение стилей (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): Проверка наличия класса.
    ```javascript
    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”
    ```
  6. Создание и добавление элементов (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).
    ```javascript
    // Создание нового элемента <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);
    }
    ```
  7. Работа с событиями (Handling Events):
    • element.addEventListener(eventType, listener, options): Добавление обработчика события.
    • element.removeEventListener(eventType, listener, options): Удаление обработчика события.
    • event: Объект события, который передаётся в обработчик. Содержит информацию о событии (тип, целевой элемент, координаты мыши и т.д.).
    • event.target: Элемент, на котором произошло событие.
    • event.preventDefault(): Предотвращение действия по умолчанию (например, переход по ссылке, отправка формы).
    • event.stopPropagation(): Остановка “всплытия” события (предотвращение распространения события на родительские элементы).
    ```javascript
    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 = ‘’; // Возвращаем исходный цвет
    });
    ```
  8. Работа с формами:
    • Доступ к элементам формы:
      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(); // Проверяем и показываем/скрываем сообщения об ошибках
       
  9. Работа с 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 и многое другое.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

В чем разница между innerHTML, textContent, appendChild?

A

Давайте подробно разберём различия между 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 полностью заменяет содержимое, а не добавляет к нему.

2. textContent

  • Назначение: Свойство textContent позволяет получить или установить текстовое содержимое элемента и его потомков, без HTML-тегов.
  • Получение содержимого: Возвращает строку, содержащую весь текст внутри элемента, включая текст вложенных элементов, но без самих тегов. Пробелы и переносы строк сохраняются.
  • Установка содержимого: Заменяет всё текущее содержимое элемента новым текстом. Любые символы, которые могут быть интерпретированы как HTML-теги (например, <, >, &), автоматически экранируются (преобразуются в HTML-сущности: &lt;, &gt;, &amp;).
  • Пример:```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">Это &lt;новый&gt; абзац.</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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Как работает MutationObserver?

A

MutationObserver — это мощный API в JavaScript, который позволяет асинхронно отслеживать изменения в DOM-дереве. Он предоставляет способ реагировать на добавление, удаление или изменение узлов, изменение атрибутов и изменение текстового содержимого элементов. Это значительно более эффективный и гибкий подход по сравнению со старыми, ныне устаревшими, Mutation Events.

Как работает MutationObserver:

  1. Создание экземпляра:
    • Вы создаете новый экземпляр MutationObserver с помощью конструктора, передавая ему функцию обратного вызова (callback). Эта функция будет вызываться каждый раз, когда происходят отслеживаемые изменения.
    javascript
    const observer = new MutationObserver(callback);
  2. Настройка наблюдения (конфигурация):
    • Вы вызываете метод 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, но будут отслеживаться изменения только перечисленных атрибутов.
  3. Функция обратного вызова (Callback):
    • Функция, которую вы передаёте в конструктор MutationObserver, вызывается асинхронно после того, как произошли изменения в DOM.
    • Функция обратного вызова получает два аргумента:
      • mutations: Массив объектов MutationRecord. Каждый объект MutationRecord описывает одно произошедшее изменение.
      • observer: Сам экземпляр MutationObserver. Это может быть полезно, если вы используете одну и ту же функцию обратного вызова для нескольких наблюдателей.
  4. Объект 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 в конфигурации).
  5. Остановка наблюдения:
    • Чтобы прекратить наблюдение, вызовите метод disconnect() у экземпляра MutationObserver.
    javascript
    observer.disconnect();
  6. 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, не перегружая браузер.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Что такое Intersection Observer и где его используют?

A

IntersectionObserver — это современный веб-API, который предоставляет простой и эффективный способ асинхронно отслеживать, когда элемент (или группа элементов) появляется в области видимости (viewport) браузера или другого элемента-контейнера (root element). Он позволяет реагировать на пересечение целевого элемента с областью видимости, не прибегая к постоянным проверкам положения элемента на странице, что значительно улучшает производительность.

Как работает IntersectionObserver:

  1. Создание экземпляра:
    • Вы создаете новый экземпляр IntersectionObserver с помощью конструктора, передавая ему функцию обратного вызова (callback) и необязательный объект с настройками (options).
    javascript
    const observer = new IntersectionObserver(callback, options);
  2. Настройка наблюдения (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].
  3. Начало наблюдения:
    • Вы вызываете метод observe() у созданного экземпляра, передавая ему целевой элемент (или элементы), за которым нужно наблюдать.
    javascript
    observer.observe(targetElement);
  4. Функция обратного вызова (Callback):
    • Функция, которую вы передаёте в конструктор IntersectionObserver, вызывается асинхронно при каждом пересечении целевого элемента с областью видимости (или при выходе из неё).
    • Функция обратного вызова получает два аргумента:
      • entries: Массив объектов IntersectionObserverEntry. Каждый объект IntersectionObserverEntry описывает состояние пересечения одного наблюдаемого элемента.
      • observer: Сам экземпляр IntersectionObserver.
  5. Объект IntersectionObserverEntry:
    • Каждый объект IntersectionObserverEntry в массиве entries содержит информацию о пересечении одного целевого элемента. Основные свойства:
      • boundingClientRect: Объект DOMRect, описывающий размер и положение целевого элемента относительно окна браузера.
      • intersectionRatio: Число от 0 до 1, указывающее, какая доля целевого элемента видна в области видимости.
      • intersectionRect: Объект DOMRect, описывающий размер и положение пересекающейся части целевого элемента (той части, которая видна).
      • isIntersecting: true, если целевой элемент пересекает область видимости (хотя бы частично), false в противном случае.
      • rootBounds: Объект DOMRect, описывающий размер и положение root элемента (или viewport, если root не указан).
      • target: Сам наблюдаемый DOM-элемент.
      • time: Временная метка (timestamp), когда произошло пересечение.
  6. Остановка наблюдения:
    • Чтобы прекратить наблюдение за конкретным элементом, вызовите метод unobserve() у экземпляра IntersectionObserver, передав ему этот элемент.
    javascript
    observer.unobserve(targetElement);
    • Чтобы прекратить наблюдение за всеми элементами, вызовите метод disconnect().
    javascript
    observer.disconnect();

Где используют IntersectionObserver:

  1. Ленивая загрузка изображений (Lazy Loading):
    • Загрузка изображений только тогда, когда они попадают в область видимости пользователя. Это значительно ускоряет загрузку страницы и экономит трафик.
    ```javascript
    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);
    });
    ```
  2. Бесконечная прокрутка (Infinite Scroll):
    • Подгрузка нового контента, когда пользователь прокручивает страницу до определённого места (например, до конца текущего списка).
    ```javascript
    const loader = document.getElementById(‘loader’); // Элемент-индикатор загрузкиconst observer = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting) {
    // Загружаем следующую порцию данных
    loadMoreData();
    }
    });observer.observe(loader);
    ```
  3. Отложенное выполнение анимаций:
    • Запуск анимаций только тогда, когда элемент появляется в области видимости.
    ```javascript
    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);
    });
    ```
  4. Отслеживание видимости рекламы:
    • Определение, видит ли пользователь рекламный блок, для более точного подсчёта показов.
  5. Определение, читает ли пользователь статью:
    • Можно отслеживать, находится ли заголовок следующего раздела статьи в области видимости, чтобы определить, дочитал ли пользователь до этого места.
  6. Прилипчивые (sticky) элементы:
    • Реализация “прилипчивых” заголовков или панелей навигации, которые остаются видимыми при прокрутке. Хотя это можно сделать и с помощью CSS (position: sticky), IntersectionObserver может быть полезен в более сложных случаях.
  7. Воспроизведение/пауза видео:
    • Автоматическое воспроизведение видео, когда оно попадает в область видимости, и приостановка, когда оно выходит из неё.
  8. Аналитика:
    • Отслеживание, какие секции страницы пользователь реально просматривает.

Преимущества 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 — это очень полезный инструмент для оптимизации производительности и создания интерактивных веб-страниц. Он позволяет легко и эффективно отслеживать видимость элементов и реагировать на неё.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Как работают localStorage, sessionStorage, cookies?

A

localStorage, sessionStorage и cookies — это механизмы хранения данных на стороне клиента (в браузере), которые позволяют веб-сайтам сохранять информацию между сеансами просмотра или в рамках одного сеанса. У каждого из них свои особенности, ограничения и сценарии использования.

1. Cookies (Куки)

  • Назначение: Cookies — это небольшие текстовые файлы, которые веб-серверы могут хранить на компьютере пользователя через браузер. Они используются для хранения различной информации, такой как настройки пользователя, данные аутентификации, идентификаторы сеансов, содержимое корзины покупок и т.д.
  • Механизм работы:
    • Установка: Веб-сервер отправляет cookie браузеру в HTTP-заголовке Set-Cookie. Браузер сохраняет cookie и связывает его с доменом, который его установил.
    • Чтение: При каждом последующем запросе к тому же домену браузер автоматически отправляет cookie обратно серверу в HTTP-заголовке Cookie.
    • Удаление: Сервер может удалить cookie, отправив новый Set-Cookie с датой истечения срока действия в прошлом. Также cookie может быть удален, если истек его срок действия, заданный при установке.
  • Свойства (атрибуты) 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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Как работает Fetch API и чем он лучше XMLHttpRequest?

A

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:

  1. Промисы: Fetch API использует промисы, что делает асинхронный код более читаемым и управляемым, чем обратные вызовы, часто используемые с XHR.
  2. Объект Response: Предоставляет методы для обработки тела ответа в различных форматах (JSON, текст, Blob, ArrayBuffer, FormData).
  3. Объект Request: Позволяет создавать и настраивать запросы с указанием метода (GET, POST, PUT, DELETE и т. д.), заголовков, тела запроса и других параметров.
  4. Потоки (Streams): Fetch API поддерживает потоковую обработку данных, что позволяет работать с большими объемами данных, не загружая их полностью в память.
  5. Кеширование: Управление кешированием запросов с помощью опций кеша (cache).
  6. Перенаправления: Управление перенаправлениями с помощью опции redirect.
  7. Аборты запросов: Возможность отмены запроса с помощью AbortController.
  8. Credentials: Управление отправкой учетных данных (куки, заголовки авторизации) с помощью опции credentials.

Преимущества Fetch API перед XMLHttpRequest:

  1. Более чистый и современный синтаксис: Fetch API использует промисы, что делает код более читаемым и простым для понимания, особенно при выполнении нескольких последовательных запросов.
  2. Более гибкая настройка запросов: Fetch API предоставляет объект Request, который позволяет более детально настраивать запросы.
  3. Потоковая обработка: Поддержка потоков позволяет эффективно работать с большими данными.
  4. Лучшая обработка ошибок: Fetch API обрабатывает сетевые ошибки (например, 404, 500) как отклоненные промисы, что упрощает обработку ошибок. В XHR для этого нужно проверять status и readyState.
  5. Встроенная поддержка JSON: В Fetch API есть удобный метод response.json(), который автоматически парсит JSON-ответ. В XHR для этого нужно использовать JSON.parse().
  6. Поддержка в современных браузерах: Fetch API поддерживается всеми современными браузерами. Для старых браузеров можно использовать полифилы.

Недостатки Fetch API:

  1. Нет встроенной поддержки прогресса загрузки/выгрузки: Для отслеживания прогресса нужно использовать Readable Streams или другие подходы. В XHR есть события progress.
  2. Не поддерживает таймауты из коробки: Для реализации таймаутов нужно использовать AbortController или другие техники. В XHR есть свойство timeout.
  3. Обработка ошибок, связанных с сетью, происходит на уровне catch, а ошибки, связанные с HTTP-статусами (4xx, 5xx), нужно обрабатывать, проверяя response.ok или response.status.

В итоге:

Fetch API — это мощный и современный инструмент для выполнения сетевых запросов в JavaScript. Он предоставляет более удобный и гибкий интерфейс по сравнению с XMLHttpRequest. Несмотря на некоторые недостатки, Fetch API является предпочтительным выбором для большинства задач, связанных с сетевыми запросами.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Что такое CORS и как он работает?

A

CORS (Cross-Origin Resource Sharing — «совместное использование ресурсов между разными источниками») — это механизм безопасности браузера, который ограничивает веб-страницам доступ к ресурсам с другого домена (источника). Он является частью политики одинакового источника (Same-Origin Policy, SOP), которая предотвращает выполнение вредоносных скриптов с одного сайта на другом.

Что такое “источник”?

Источник (origin) определяется комбинацией трёх составляющих:

  1. Протокол: http или https.
  2. Домен: example.com, www.example.com, api.example.com.
  3. Порт: :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), браузер выполняет следующие действия:

  1. Preflight запрос (предварительный запрос) (не всегда):
    • Для “непростых” запросов (запросов, которые могут изменить данные на сервере, например, POST, PUT, DELETE, или запросов с нестандартными заголовками) браузер сначала отправляет предварительный запрос OPTIONS к целевому ресурсу.
    • Этот запрос OPTIONS содержит заголовки, указывающие, какой метод (Access-Control-Request-Method) и какие заголовки (Access-Control-Request-Headers) будут использоваться в фактическом запросе.
    • Сервер должен ответить на этот OPTIONS запрос с соответствующими CORS-заголовками (см. ниже), разрешающими или запрещающими запрос.
    • Если сервер не разрешает запрос (нет нужных заголовков или они не соответствуют требованиям), браузер блокирует фактический запрос, и в консоли появляется ошибка CORS.
    • Если сервер разрешает запрос, браузер переходит к шагу 2.
  2. Фактический запрос:
    • Для “простых” запросов (обычно GET, HEAD, POST с Content-Type: application/x-www-form-urlencoded, multipart/form-data, text/plain) или если preflight запрос был успешен, браузер отправляет фактический запрос к целевому ресурсу.
    • Браузер автоматически добавляет к запросу заголовок Origin, указывающий источник запроса (например, Origin: https://www.example.com).
  3. Ответ сервера:
    • Сервер обрабатывает запрос и отправляет ответ.
    • Чтобы разрешить кросс-доменный запрос, сервер должен включить в ответ специальные 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.
  • 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.
  • Access-Control-Max-Age: (Используется в ответе на preflight запрос OPTIONS)
    • Указывает, как долго (в секундах) браузер может кешировать результаты preflight запроса. Это позволяет избежать повторных preflight запросов для একই URL в течение указанного времени.
    • Пример: Access-Control-Max-Age: 3600 (кешировать на 1 час).

Пример (с preflight запросом):

  1. Браузер (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
  2. Сервер (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
  3. Браузер (https://www.example.com):
    ```
    POST /data HTTP/1.1
    Origin: https://www.example.com
    Content-Type: application/json
    Authorization: Bearer mytoken{ “message”: “Hello” }
    ```
  4. Сервер (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, необходимо для разработки веб-приложений, которые взаимодействуют с ресурсами на разных доменах.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Как работают Service Workers и зачем они нужны?

A

Service Workers — это скрипты, которые браузер запускает в фоновом режиме, отдельно от веб-страницы. Они предоставляют возможности, которые раньше были доступны только нативным приложениям, такие как:

  • Работа в офлайн-режиме: Кеширование ресурсов и обработка запросов, когда нет подключения к интернету.
  • Фоновая синхронизация данных: Обновление данных в фоновом режиме, даже если приложение неактивно.
  • Push-уведомления: Отображение уведомлений пользователю, даже если браузер закрыт (с разрешения пользователя).
  • Перехват сетевых запросов: Полный контроль над сетевыми запросами, что позволяет реализовать сложные стратегии кеширования, проксирование и многое другое.

Как работают Service Workers:

  1. Регистрация:
    • Веб-страница регистрирует Service Worker с помощью JavaScript API (navigator.serviceWorker.register()).
    • При регистрации указывается путь к JavaScript-файлу Service Worker (например, /sw.js).
    • Браузер загружает этот файл и запускает его в отдельном контексте (Service Worker context).
  2. Установка (Install):
    • После успешной регистрации Service Worker переходит в состояние установки.
    • В обработчике события install (внутри файла sw.js) обычно выполняется кеширование статических ресурсов (HTML, CSS, JavaScript, изображения).
    • Если установка прошла успешно, Service Worker переходит в состояние ожидания (waiting).
    • Если во время установки произошла ошибка (например, не удалось скачать ресурсы), Service Worker отбрасывается.
  3. Активация (Activate):
    • Service Worker активируется, когда:
      • Нет активного Service Worker для данного scope (области действия).
      • Произошло обновление файла Service Worker.
      • Вызван метод clients.claim() (позволяет новому Service Worker немедленно взять под контроль все страницы в своем scope).
    • В обработчике события activate обычно удаляются старые кеши, которые больше не нужны.
    • После активации Service Worker начинает контролировать все страницы в своем scope.
  4. Жизненный цикл:
    • 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 или отброшен из-за ошибки.
  5. Обработка событий:
    • Service Worker работает на основе событий. Основные события:
      • install: Срабатывает при установке Service Worker.
      • activate: Срабатывает при активации Service Worker.
      • fetch: Срабатывает при каждом сетевом запросе из контролируемых страниц. Позволяет перехватывать запросы, изменять их, возвращать ответы из кеша или выполнять другие действия.
      • push: Срабатывает при получении push-уведомления от сервера.
      • sync: Срабатывает при фоновой синхронизации данных.
      • message: Позволяет обмениваться сообщениями между 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 — это мощный инструмент, который открывает новые возможности для веб-разработки. Они позволяют создавать быстрые, надежные и удобные веб-приложения, которые работают даже в офлайн-режиме.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

В чем разница между WebSockets, SSE и Fetch API?

A

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 приложения | Новостные ленты, уведомления, мониторинг |

How well did you know this?
1
Not at all
2
3
4
5
Perfectly