DOM и браузер
DOM (Document Object Model) — это живое дерево объектов, в которое браузер превращает HTML-разметку. JavaScript не работает с текстом страницы напрямую: он находит узлы этого дерева, читает и меняет их свойства, вешает на них слушатели событий. Понимать DOM — значит знать, чем поиск по id отличается от поиска по CSS-селектору, почему innerHTML опасен для недоверенных данных и как одно событие проходит сквозь всех предков элемента.
Тема собирает четыре пласта: поиск и изменение узлов, различие атрибутов и свойств, модель событий (регистрация, объект Event, три фазы распространения, делегирование, отмена) и пользовательские элементы с их жизненным циклом. Ловушки здесь практичны — от стирания классов через className до XSS-инъекции. Полная карта — ниже.
Карта темы
- Поиск элементов —
getElementById,querySelectorиquerySelectorAll; статическийNodeListпротив живой коллекции. - Изменение содержимого —
textContent,innerHTML,style, атрибуты иclassList, а также риск XSS при вставке разметки. - Атрибуты против свойств — строка исходной разметки против живого значения DOM-объекта; почему
classсоответствуетclassName. - Обработка событий —
addEventListener, объектEventиremoveEventListenerпо той же ссылке. - Распространение событий — три фазы, всплытие и погружение, делегирование и отмена потока (
preventDefault,stopPropagation). - Пользовательские элементы —
customElements.define, коллбэки жизненного цикла и инкапсуляция через shadow DOM.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать querySelectorAll живой коллекцией | На деле это статический NodeList — не обновляется при изменениях DOM |
Ждать null от querySelectorAll при отсутствии совпадений | Он возвращает пустой список, а не null |
Присваивать className ради одного класса | Стирает все остальные классы; нужен classList.add |
Вставлять недоверенный текст через innerHTML | Открывает XSS-инъекцию; для текста берите textContent |
Ждать от getAttribute('value') введённый текст | Он даёт исходную разметку, а не живое свойство input.value |
| Считать имена атрибута и свойства одинаковыми | class → className, for → htmlFor и т.д. |
Думать, что второй addEventListener затирает первый | Он добавляет ещё один обработчик; оба сработают |
| Передавать анонимную функцию, а потом снимать её | removeEventListener требует ту же ссылку на функцию |
Путать target и currentTarget | target — где возникло событие, currentTarget — где висит слушатель |
| Считать, что погружение идёт вверх | Наоборот: погружение вниз к цели, всплытие вверх от неё |
Ждать, что return false в addEventListener отменит default | Работает только в инлайн-обработчике; в колбэке нужен preventDefault |
| Забыть дефис в имени пользовательского элемента | customElements.define требует имя с дефисом |
Значение для собеседований
DOM спрашивают, чтобы проверить, работаете ли вы с деревом осознанно или наугад. Кандидат, который объясняет делегирование через «один слушатель на предке ловит события всех потомков за счёт всплытия, в том числе добавленных позже», сразу опережает того, кто вешает обработчик на каждую строку списка.
Что обычно проверяют:
- Разницу
getElementById/querySelector/querySelectorAllи статический vs живой характер результата. - Разницу
textContent/innerHTMLи почему второе опасно для недоверенных данных. - Разницу HTML-атрибута и DOM-свойства; несовпадение имён (
class/className). - Как
addEventListenerрегистрирует обработчик и что в объектеEvent. - Три фазы распространения, что всплытие — по умолчанию, и как работает делегирование.
- Различие
preventDefault,stopPropagation,stopImmediatePropagation.
Типичный неверный ответ: «querySelectorAll возвращает живую коллекцию, которая сама обновляется при добавлении элементов». На деле она статическая — снимок на момент вызова; живыми бывают HTMLCollection от getElementsByClassName и getElementsByTagName.