← Назад к вопросам

Как фазы событий помогают управлять порядком работы обработчиков?

2.2 Middle🔥 191 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Фазы событий DOM и управление порядком обработчиков

В DOM есть три фазы обработки событий: capturing, target и bubbling. Понимание этих фаз критично для правильного управления обработчиками.

Три фазы событий

┌─────────────────────────────────────────────────────────┐
│                    Window/Document                      │
│  ┌─────────────────────────────────────────────────┐   │
│  │  1. CAPTURING (спуск вниз)                      │   │
│  │  └─ document.addEventListener(..., true)       │   │
│  │  └─ body (capturing)                           │   │
│  │  └─ div (capturing)                            │   │
│  │                                                 │   │
│  │  2. TARGET (цель)                              │   │
│  │  └─ button.addEventListener(...)               │   │
│  │  └─ element.on-click                           │   │
│  │                                                 │   │
│  │  3. BUBBLING (всплытие вверх)                  │   │
│  │  └─ div (bubbling)                             │   │
│  │  └─ body (bubbling)                            │   │
│  │  └─ document.addEventListener(...)             │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

1. Capturing Phase (Фаза захвата)

События распространяются ОТ корня ДО целевого элемента.

// HTML
<div id="outer">
  <div id="middle">
    <button id="btn">Click me</button>
  </div>
</div>

// JavaScript
const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const btn = document.getElementById('btn');

// Capturing - true третий параметр
outer.addEventListener('click', () => {
  console.log('Outer (capturing)');
}, true);  // true = capturing

middle.addEventListener('click', () => {
  console.log('Middle (capturing)');
}, true);

btn.addEventListener('click', () => {
  console.log('Button (capturing)');
}, true);

// Вывод при клике на кнопку:
// Outer (capturing)
// Middle (capturing)
// Button (capturing)

Характеристики:

  • Проходит от document вниз к целевому элементу
  • Третий параметр addEventListener должен быть true
  • Используется реже, чем bubbling
  • Полезно для глобальной обработки до целевого элемента

2. Target Phase (Фаза цели)

Событие достигает целевого элемента.

// Целевой элемент
btn.addEventListener('click', () => {
  console.log('Button (target)');
});  // true или false - не важно, это target фаза

// Вывод при клике:
// Button (target) - выполняется в любом случае

Характеристики:

  • Выполняется на самом элементе
  • Всегда выполняется, независимо от порядка регистрации
  • event.target указывает на элемент

3. Bubbling Phase (Фаза всплытия)

События распространяются ОТ целевого элемента ДО корня.

// Bubbling - false третий параметр (или просто опустить)
outer.addEventListener('click', () => {
  console.log('Outer (bubbling)');
}, false);  // false или пусто = bubbling (по умолчанию)

middle.addEventListener('click', () => {
  console.log('Middle (bubbling)');
}, false);

btn.addEventListener('click', () => {
  console.log('Button (bubbling)');
}, false);

// Вывод при клике на кнопку:
// Button (bubbling)
// Middle (bubbling)
// Outer (bubbling)

Характеристики:

  • Проходит от целевого элемента вверх к document
  • Это поведение по умолчанию
  • Позволяет обрабатывать события на родительских элементах
  • Используется чаще всего

Полный пример со всеми фазами

const outer = document.getElementById('outer');
const middle = document.getElementById('middle');
const btn = document.getElementById('btn');

// CAPTURING (спуск вниз)
outer.addEventListener('click', () => {
  console.log('1. Outer (capturing)');
}, true);

middle.addEventListener('click', () => {
  console.log('2. Middle (capturing)');
}, true);

btn.addEventListener('click', () => {
  console.log('3. Button (capturing)');
}, true);

// TARGET (цель)
btn.addEventListener('click', () => {
  console.log('4. Button (target/bubbling)');
}, false);

// BUBBLING (всплытие вверх)
btn.addEventListener('click', () => {
  console.log('5. Button (bubbling)');
}, false);

middle.addEventListener('click', () => {
  console.log('6. Middle (bubbling)');
}, false);

outer.addEventListener('click', () => {
  console.log('7. Outer (bubbling)');
}, false);

// Вывод при клике на кнопку:
// 1. Outer (capturing)
// 2. Middle (capturing)
// 3. Button (capturing)
// 4. Button (target/bubbling)
// 5. Button (bubbling)
// 6. Middle (bubbling)
// 7. Outer (bubbling)

Event.stopPropagation() - Остановка распространения

Останавливает распространение события (и capturing, и bubbling).

const outer = document.getElementById('outer');
const btn = document.getElementById('btn');

outer.addEventListener('click', (e) => {
  console.log('Outer clicked');
}, false);

btn.addEventListener('click', (e) => {
  e.stopPropagation();  // Останавливает распространение
  console.log('Button clicked');
}, false);

// Вывод при клике на кнопку:
// Button clicked
// (Outer clicked НЕ выполнится - распространение остановлено)

Event.stopImmediatePropagation() - Остановка всего

Останавливает распространение И другие обработчики на том же элементе.

const btn = document.getElementById('btn');

btn.addEventListener('click', (e) => {
  e.stopImmediatePropagation();
  console.log('Handler 1');
}, false);

btn.addEventListener('click', (e) => {
  console.log('Handler 2');  // НЕ выполнится
}, false);

btn.addEventListener('click', (e) => {
  console.log('Handler 3');  // НЕ выполнится
}, false);

// Вывод:
// Handler 1
// (Handler 2 и Handler 3 не выполнятся)

Event.preventDefault() - Отмена действия

Отменяет стандартное поведение события (но распространение продолжается).

const form = document.querySelector('form');
const btn = form.querySelector('button');

form.addEventListener('submit', (e) => {
  console.log('Form submit handler');
  // e.preventDefault() не вызвана - форма отправится
}, false);

btn.addEventListener('click', (e) => {
  e.preventDefault();  // Отменяет отправку формы
  console.log('Button click handler');
}, false);

// Вывод:
// Button click handler
// (Form submit handler не выполнится - preventDefault отменил)

Event Delegation - Использование Bubbling

Назначение обработчика на родителя вместо каждого элемента.

<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
  <li>Item 5</li>
</ul>
// Плохо - назначаем обработчик каждому li
const items = document.querySelectorAll('li');
items.forEach(item => {
  item.addEventListener('click', () => {
    console.log('Clicked:', item.textContent);
  });
});

// Хорошо - используем event delegation через bubbling
const list = document.getElementById('list');
list.addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    console.log('Clicked:', e.target.textContent);
  }
});

// Даже если добавим новые li динамически, обработчик работает!
list.innerHTML += '<li>Item 6</li>';  // Новый элемент - обработчик работает

Практический пример: Управление фокусом и валидацией

const form = document.querySelector('form');

// CAPTURING - перехватываем ДО целевого элемента
form.addEventListener('focus', (e) => {
  console.log('Capturing phase - preparing to focus on:', e.target.name);
}, true);

// TARGET PHASE - обработка целевого элемента
const inputs = form.querySelectorAll('input');
inputs.forEach(input => {
  input.addEventListener('focus', (e) => {
    e.target.classList.add('focused');
    console.log('Target phase - focused on:', e.target.name);
  });
});

// BUBBLING - обработка после целевого элемента
form.addEventListener('focus', (e) => {
  console.log('Bubbling phase - just focused on:', e.target.name);
  
  // Логирование всех фокусов
  console.log('Current focused element:', document.activeElement);
}, false);

Таблица событий, которые НЕ всплывают

СобытиеВсплывает?
clickДа
dblclickДа
mousedown/mouseupДа
mousemoveНет
mouseover/mouseoutДа
focusНет (используй focusin)
blurНет (используй focusout)
scrollНет
resizeНет
loadНет

Если нужно bubbling для focus - используй focusin:

// focus НЕ всплывает
input.addEventListener('focus', () => { });

// focusin - всплывает!
form.addEventListener('focusin', (e) => {
  console.log('Input focused:', e.target);  // Работает!
}, false);

React и обработчики событий

React использует синтетические события (synthetic events), которые работают немного по-другому:

// React обработчики используют bubbling по умолчанию
function Button() {
  const handleClick = (e) => {
    console.log('Button clicked');
    // e.stopPropagation() - работает, но для синтетических событий
  };
  
  return <button onClick={handleClick}>Click</button>;
}

// Для capturing в React - используй onClickCapture
function Container() {
  const handleClickCapture = (e) => {
    console.log('Capturing phase in React');
  };
  
  return (
    <div onClickCapture={handleClickCapture}>
      <Button />
    </div>
  );
}

Лучшие практики

1. Используй event delegation где возможно:

// Хорошо
list.addEventListener('click', (e) => {
  if (e.target.matches('li')) {
    handleItemClick(e.target);
  }
});

// Плохо - много обработчиков
items.forEach(item => {
  item.addEventListener('click', handleItemClick);
});

2. Явно указывай фазу:

// Ясно - capturing
element.addEventListener('click', handler, true);

// Ясно - bubbling (явно, хотя по умолчанию)
element.addEventListener('click', handler, false);

// Менее ясно - используется поведение по умолчанию
element.addEventListener('click', handler);

3. Удаляй обработчики при очистке:

const handler = (e) => console.log('clicked');

element.addEventListener('click', handler);

// Позже, при unmount компонента
element.removeEventListener('click', handler);

Заключение

Фазы событий позволяют:

  • Управлять порядком выполнения обработчиков
  • Event delegation - обрабатывать множество элементов одним обработчиком
  • Контролировать распространение - останавливать дальнейшее распространение
  • Оптимизировать производительность - меньше обработчиков = меньше памяти

Основной инструмент - это bubbling фаза и event delegation, которые используются в большинстве приложений.

Как фазы событий помогают управлять порядком работы обработчиков? | PrepBro