Skip to Content
JavaScriptСобытия в DOM

События в DOM

Уровень: middle.

Вопрос

Как событие идёт по дереву и зачем вешать один слушатель на родителя?

Короткий ответ

Событие идёт в три фазы: захват от window к цели, сама цель, всплытие обратно к window. addEventListener по умолчанию слушает всплытие. Третий аргумент true или { capture: true } включает захват.

Делегирование: один слушатель на родителе ловит клики детей, потому что событие всплывает. В обработчике смотрят event.target — фактический узел, по которому кликнули. Так список не вешает слушатель на каждую строку, и новые строки работают без повторной подписки.

preventDefault() отменяет действие браузера: переход по ссылке, отправку формы. Всплытие при этом продолжается. stopPropagation() останавливает ход по дереву и не отменяет действие браузера. Это разные вещи.

Разбор

target — узел, где событие возникло. currentTarget — узел, на котором висит текущий слушатель. При делегировании они различаются.

Не все события всплывают. focus, blur, mouseenter, mouseleave не всплывают. Для делегирования фокуса берут focusin / focusout или захват.

{ once: true } снимет слушатель после первого вызова. { passive: true } обещает не вызывать preventDefault. Браузер тогда не ждёт обработчик, чтобы решить, скроллить ли. На touchmove и wheel пассивный слушатель по умолчанию в современных браузерах на корне документа.

На что смотрят на senior

Делегирование на document ловит всё, но перемешивает чужие виджеты. Слушатель ближе к списку проще снять при размонтировании. В Vue слушатель вешают не на window без onUnmounted: тот же класс утечек, что у любого SPA.

stopPropagation в общем компоненте ломает аналитику и внешний click-outside, которые слушают документ. Отменяют действие браузера, всплытие режут только с явной причиной.

Типичные ошибки

  • Путать preventDefault и stopPropagation.
  • Делегировать focus, который не всплывает.
  • Читать this внутри стрелочного обработчика и ждать узел. У стрелки нет своего this, узел берётся из currentTarget.
  • Вешать слушатель на каждую строку длинного списка.

Задача

На ul висит один слушатель клика. По клику на button внутри li нужно удалить эту li. Откуда брать узел и чем preventDefault тут не поможет?

Показать ответ

Узел берут из event.target, затем closest('li'). currentTarget — это сам ul, не строка.

preventDefault отменяет действие браузера, например переход по ссылке. Удаление строки — это уже ваш код, его preventDefault не делает. stopPropagation тоже не удаляет узел, оно только режет всплытие.

Last updated on