DOM Advanced

MutationObserver, выделение текста и Event Loop

Наблюдать за изменениями DOM через MutationObserver, работать с выделением текста через Selection API, и понять порядок выполнения: синхронный код → microtasks → tasks.

Продвинутый (Advanced)
Нужно знать:DOM-дерево

Кратко

MutationObserver следит за изменениями DOM (добавление/удаление узлов, изменение атрибутов). Selection API читает выделенный пользователем текст. Event Loop определяет порядок выполнения: синхронный код → microtasks (Promise callbacks) → tasks (setTimeout callbacks).


Подробно

Этот урок — три самостоятельные темы, объединённые одной идеей «браузер уведомляет ваш код». Их можно читать по отдельности: наблюдение за DOM (MutationObserver), выделение текста (Selection API) и порядок выполнения асинхронного кода (Event Loop).


1. MutationObserver — наблюдение за изменениями DOM

Обычный код работает «по запросу»: вы вызвали функцию — она выполнилась. MutationObserver работает наоборот: вы говорите браузеру «следи за этим узлом», и он сам вызывает ваш колбэк каждый раз, когда DOM меняется.

js
const observer = new MutationObserver((mutations) => {
  mutations.forEach(mutation => {
    console.log('Тип:', mutation.type);
    console.log('Добавлены:', mutation.addedNodes);
    console.log('Удалены:', mutation.removedNodes);
  });
});

observer.observe(document.body, {
  childList: true,     // добавление/удаление дочерних узлов
  attributes: true,    // изменение атрибутов
  subtree: true,       // следить за всем поддеревом
  characterData: true  // изменение текста
});

observer.disconnect(); // остановить наблюдение

Важно: колбэк MutationObserver — это microtask, а не task. Он выполняется после текущего синхронного кода, но до setTimeout.


2. Selection API — выделение текста

Selection API читает текст, который выделил пользователь (мышью или с клавиатуры), и позволяет управлять выделением программно. Range описывает часть документа, а Selection — текущее выделение (набор из одного или нескольких Range).

js
const selection = document.getSelection();
const selectedText = selection.toString();
console.log('Выделено:', selectedText);

// Выделить текст программно
const range = document.createRange();
range.selectNodeContents(document.querySelector('.text'));
selection.removeAllRanges();
selection.addRange(range);

3. Event Loop — порядок выполнения кода

Вы уже знаете, что колбэки Promise попадают в очередь микрозадач (уроки JA4 и JA5). Event Loop — это «диспетчер», который решает, что выполнять следующим. Зачем он нужен: JavaScript однопоточный, поэтому порядок «что выполняется сейчас, а что потом» обязан быть предсказуемым.

Порядок выполнения:

  • Синхронный код (call stack) — выполняется сразу, до конца.
  • Microtasks (Promise.then, queueMicrotask, MutationObserver) — после синхронного кода, все подряд.
  • Tasks (setTimeout, setInterval, I/O) — по одному, между отрисовками.
js
console.log('1. Синхронный код');

setTimeout(() => {
  console.log('4. Task (setTimeout)');
}, 0);

Promise.resolve().then(() => {
  console.log('3. Microtask (Promise)');
});

queueMicrotask(() => {
  console.log('3. Microtask (queueMicrotask)');
});

console.log('2. Синхронный код');

// Вывод: 1, 2, 3, 3, 4

Микрозадачи (microtasks): Promise.then/catch/finally, queueMicrotask(), колбэк MutationObserver. Задачи (tasks): setTimeout, setInterval, обработка I/O.


Ментальная модель

Event Loop — как диспетчер в такси. Синхронный код — текущий пассажир (ездим пока не высадится). Microtasks — VIP-пассажиры (высаживаем всех перед следующим рейсом). Tasks — обычные пассажиры (высаживаем по одному в каждом рейсе). MutationObserver — VIP-звонок: срабатывает после текущей поездки, но до следующей.


Примеры

**Самый простой:**

javascript
// MutationObserver
const target = document.querySelector('#list');

const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    console.log('Изменение:', mutation.type);
  }
});

observer.observe(target, { childList: true });

// Добавляем элемент — observer сработает
target.innerHTML += '<li>Новый</li>';

Простейший MutationObserver: следим за добавлением элементов.

**Простой:**

javascript
// Порядок выполнения
console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

queueMicrotask(() => console.log('4'));

console.log('5');

// Вывод: 1, 5, 3, 4, 2
// Синхронный → Microtasks → Tasks

Классический пример порядка: sync → microtask → task.

**Реальный пример:**

javascript
// Динамический список с отслеживением
const list = document.querySelector('#todo-list');
let itemCount = 0;

const observer = new MutationObserver((mutations) => {
  for (const mutation of mutations) {
    if (mutation.type === 'childList') {
      itemCount += mutation.addedNodes.length - mutation.removedNodes.length;
      document.querySelector('#count').textContent = itemCount;
    }
  }
});

observer.observe(list, { childList: true });

// Добавляем элементы — счётчик обновляется автоматически
function addItem(text) {
  const li = document.createElement('li');
  li.textContent = text;
  list.appendChild(li);
}

Реальный паттерн: автоматический подсчёт элементов через MutationObserver.


Частые ошибки

**Неправильно:** Забывать observer.disconnect()

**Почему:** Без disconnect observer работает бесконечно — утечка памяти.

**Правильно:** Всегда отключайте observer, когда он больше не нужен.

**Неправильно:** Думать, что MutationObserver callback — task

**Почему:** MutationObserver callback — microtask. Он выполняется ДО setTimeout.

**Правильно:** Microtasks: Promise, queueMicrotask, MutationObserver. Tasks: setTimeout, setInterval.

**Неправильно:** Путать Selection и Range

**Почему:** Selection — текущее выделение пользователя. Range — диапазон в DOM.

**Правильно:** Selection содержит Range(ы). Range описывает часть дерева.


Важно запомнить

  • MutationObserver следит за изменениями DOM
  • observer.observe(target, options) — начать
  • observer.disconnect() — остановить
  • MutationObserver callback = microtask
  • Event Loop: sync → microtasks → tasks
  • Selection API читает выделенный текст

Связь

Назад: Вы знаете DOM (D1-D8), события (DA1-DA10) и асинхронный JavaScript (JA1-JA7). Теперь вы видите полную картину: изменения DOM, события и порядок выполнения.

Вперёд: Вы завершили DOM Advanced! Основной путь курса пройден. Дальше по желанию — JS Advanced: дополнительные возможности языка.


Читайте дальше

Материалы курса DOM Advanced — PROlab Academy.

Основной путь завершён 🎉

Вы прошли обязательную программу: Инструменты → HTML → CSS → JS Core → DOM Basics → JS Intermediate → JS Async → DOM Advanced.

Продвинутый курс (JS Advanced) — по желанию