\n```\n\n### Зачем нужно всплытие\n\n**1. Event Delegation (Делегирование событий)**\n\nВсплытие позволяет обрабатывать события множественных элементов одним обработчиком:\n\n```javascript\n// ПЛОХО - обработчик для каждого элемента (неэффективно)\nconst buttons = document.querySelectorAll('button');\nbuttons.forEach(button => {\n button.addEventListener('click', (e) => {\n console.log('Button clicked:', e.target.id);\n });\n});\n\n// При добавлении новой кнопки нужно добавить обработчик - сложно!\nconst newButton = document.createElement('button');\ncontainer.appendChild(newButton);\nnewButton.addEventListener('click', ...); // Снова!\n\n// ХОРОШО - один обработчик на родителе (event delegation)\nconst container = document.getElementById('buttons-container');\ncontainer.addEventListener('click', (e) => {\n if (e.target.tagName === 'BUTTON') {\n console.log('Button clicked:', e.target.id);\n }\n});\n\n// Новые кнопки автоматически работают!\nconst newButton = document.createElement('button');\nnewButton.textContent = 'New Button';\ncontainer.appendChild(newButton); // Обработчик уже есть!\n```\n\n**Real-world пример с динамическими элементами:**\n\n```javascript\n// Список товаров с кнопками удаления\nconst productList = document.getElementById('product-list');\n\n// Один обработчик для всех кнопок (текущих и будущих)\nproductList.addEventListener('click', (e) => {\n if (e.target.classList.contains('delete-btn')) {\n const productId = e.target.closest('.product-item').dataset.id;\n deleteProduct(productId);\n }\n \n if (e.target.classList.contains('edit-btn')) {\n const productId = e.target.closest('.product-item').dataset.id;\n editProduct(productId);\n }\n});\n\n// Добавить новый товар\nfunction addProduct(product) {\n const html = `\n
\n ${product.name}\n \n \n
\n `;\n productList.insertAdjacentHTML('beforeend', html);\n // Кнопки сразу работают - обработчик уже на productList!\n}\n```\n\n**2. Общие операции для множественных элементов**\n\n```javascript\n// Обработка клика на любой элемент в таблице\nconst table = document.getElementById('users-table');\n\ntable.addEventListener('click', (e) => {\n const cell = e.target.closest('td');\n if (!cell) return; // Клик не на ячейку\n \n const row = cell.closest('tr');\n const userId = row.dataset.userId;\n \n if (e.target.tagName === 'A') {\n // Ссылка в ячейке\n e.preventDefault();\n viewUser(userId);\n }\n \n if (cell.classList.contains('editable')) {\n // Редактируемая ячейка\n makeEditable(cell);\n }\n});\n```\n\n### Control Flow: Останавливать всплытие\n\n**`e.stopPropagation()` - остановить всплытие**\n\n```javascript\n// Родитель\nparent.addEventListener('click', (e) => {\n console.log('Parent clicked');\n});\n\n// Ребёнок со stopPropagation\nbutton.addEventListener('click', (e) => {\n e.stopPropagation(); // Останавливаем всплытие\n console.log('Button clicked');\n});\n\n// Клик на button выведет:\n// Button clicked\n// (Parent clicked НЕ выведется!)\n```\n\n**Когда использовать stopPropagation:**\n\n```javascript\n// 1. Модальное окно - не нужно обрабатывать клики за его границами\nconst modal = document.getElementById('modal');\nmodal.addEventListener('click', (e) => {\n e.stopPropagation(); // Клики внутри модала не влияют на остальное\n});\n\n// Фон модала\ndocument.addEventListener('click', (e) => {\n closeModal(); // Вызовется при клике вне модала\n});\n\n// 2. Кнопка \"Ещё\" в списке - не нужно открывать строку при клике\nlist.addEventListener('click', (e) => {\n if (e.target.classList.contains('expand-btn')) {\n const item = e.target.closest('li');\n item.classList.toggle('expanded'); // Это хорошо\n }\n});\n\nlist.addEventListener('click', (e) => {\n const item = e.target.closest('li');\n if (!e.target.classList.contains('expand-btn')) {\n selectItem(item); // Но это вызовется даже при клике на expand-btn!\n }\n});\n\n// ЛУЧШЕ:\nbutton.addEventListener('click', (e) => {\n e.stopPropagation(); // Останавливаем всплытие\n expandItem();\n});\n```\n\n### `e.preventDefault()` vs `e.stopPropagation()`\n\n**Это РАЗНЫЕ вещи:**\n\n```javascript\n// preventDefault() - отменяет DEFAULT ACTION элемента\nlink.addEventListener('click', (e) => {\n e.preventDefault(); // Не следовать ссылке\n loadPageViaAjax(href); // Вместо этого загрузить AJAX\n // Всплытие ВСЕ ЕЩЕ происходит!\n});\n\n// stopPropagation() - останавливает всплытие события\nbutton.addEventListener('click', (e) => {\n e.stopPropagation(); // Событие не поднимется дальше\n // Но default action всё ещё может произойти!\n});\n\n// Может потребоваться оба\nbutton.addEventListener('click', (e) => {\n e.preventDefault(); // Отменить default action\n e.stopPropagation(); // И остановить всплытие\n handleClick();\n});\n```\n\n### Event Capture vs Bubbling\n\n**Фаза Capture (противоположна Bubbling)**\n\n```javascript\n// По умолчанию - Bubbling фаза (3-й параметр = false)\nbutton.addEventListener('click', handler, false); // Bubbling\n\n// Capture фаза - событие идёт вниз (3-й параметр = true)\nparent.addEventListener('click', handler, true); // Capture\n\n// Пример:\nwindow.addEventListener('click', () => console.log('1. Window Capture'), true);\nwindow.addEventListener('click', () => console.log('2. Window Bubble'), false);\n\ndocument.addEventListener('click', () => console.log('3. Document Capture'), true);\ndocument.addEventListener('click', () => console.log('4. Document Bubble'), false);\n\nbutton.addEventListener('click', () => console.log('5. Button Target'));\n\n// Клик на button выведет:\n// 1. Window Capture (идёт вниз)\n// 3. Document Capture (идёт вниз)\n// 5. Button Target (цель)\n// 4. Document Bubble (идёт вверх)\n// 2. Window Bubble (идёт вверх)\n```\n\n### React: Event Delegation уже встроена\n\n**React использует event delegation под капотом**\n\n```typescript\nfunction UserList({ users }) {\n const handleDelete = (userId: string) => {\n // React delegate события на корневой элемент\n // Поэтому работает эффективно\n deleteUser(userId);\n };\n \n return (\n \n );\n}\n\n// React: все обработчики onClick делегированы на root element\n// Это очень эффективно для больших списков!\n```\n\n### Best Practices\n\n```javascript\n// 1. Используй event delegation для динамических элементов\ncontainer.addEventListener('click', (e) => {\n const action = e.target.dataset.action;\n if (action === 'delete') {\n delete(e.target.dataset.id);\n }\n});\n\n// 2. Используй closest() для поиска нужного элемента\ncontainer.addEventListener('click', (e) => {\n const item = e.target.closest('[data-item-id]');\n if (item) {\n console.log('Item:', item.dataset.itemId);\n }\n});\n\n// 3. Проверяй classList или атрибуты\nif (e.target.classList.contains('delete-button')) {\n // Это кнопка удаления\n}\n\n// 4. Используй stopPropagation() только когда необходимо\n// stopPropagation() затрудняет отладку и может сломать другой код\nbutton.addEventListener('click', (e) => {\n e.stopPropagation();\n // Хорошая причина\n});\n\n// 5. Удаляй обработчики когда элементы удаляются (утечки памяти)\nconst item = document.createElement('div');\nitem.addEventListener('click', handler);\n// ...\nitem.removeEventListener('click', handler);\nitem.remove();\n```\n\n### Performance: Event Delegation\n\n**Event Delegation намного эффективнее**\n\n```javascript\n// ПЛОХО - 1000 обработчиков для 1000 элементов\nconst items = document.querySelectorAll('.item');\nitems.forEach(item => {\n item.addEventListener('click', handleClick);\n // 1000 обработчиков! Много памяти, медленно добавлять/удалять\n});\n\n// ХОРОШО - 1 обработчик для 1000 элементов\nconst container = document.getElementById('items');\ncontainer.addEventListener('click', (e) => {\n if (e.target.classList.contains('item')) {\n handleClick(e);\n // 1 обработчик! Быстро, экономит память\n }\n});\n```\n\n### Итог\n\n**Event Bubbling нужно для:**\n\n1. **Event Delegation** - один обработчик для множества элементов\n2. **Эффективность** - меньше обработчиков = меньше памяти\n3. **Динамические элементы** - новые элементы сразу работают\n4. **Упрощение кода** - меньше обработчиков = проще поддерживать\n\nВсплытие — это не просто техническая деталь, а мощный инструмент для написания эффективного и гибкого JavaScript кода. Event delegation — один из самых важных паттернов в DOM программировании.","dateCreated":"2026-04-02T22:09:51.003228","upvoteCount":0,"author":{"@type":"Person","name":"claude-haiku-4.5"}}}}
← Назад к вопросам

Для чего нужно всплытие событий?

2.0 Middle🔥 121 комментариев
#JavaScript Core

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

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

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

Event Bubbling: назначение и применение

Event Bubbling (всплытие событий) — это механизм JavaScript, при котором событие, произошедшее на элементе, распространяется вверх по иерархии DOM до корневого элемента. Это не просто техническая деталь, а мощный инструмент для разработки эффективного и гибкого кода.

Как работает Event Bubbling

DOM тек и фазы событий:

<!-- HTML структура -->
<div id="parent" style="border: 1px solid blue; padding: 20px;">
  <div id="child" style="border: 1px solid red; padding: 20px;">
    <button id="button">Клик</button>
  </div>
</div>

<script>
// События распространяются в этом порядке:
// 1. Capture phase: window -> document -> html -> body -> parent -> child -> button
// 2. Target phase: button (цель события)
// 3. Bubbling phase: button -> child -> parent -> body -> html -> document -> window

document.getElementById('button').addEventListener('click', (e) => {
  console.log('1. Button clicked');
  // e.target = button
  // e.currentTarget = button
});

document.getElementById('child').addEventListener('click', (e) => {
  console.log('2. Bubbled to child');
  // e.target = button (оригинальный элемент)
  // e.currentTarget = child (элемент с обработчиком)
});

document.getElementById('parent').addEventListener('click', (e) => {
  console.log('3. Bubbled to parent');
  // e.target = button
  // e.currentTarget = parent
});

// Клик на button выведет:
// 1. Button clicked
// 2. Bubbled to child
// 3. Bubbled to parent
</script>

Зачем нужно всплытие

1. Event Delegation (Делегирование событий)

Всплытие позволяет обрабатывать события множественных элементов одним обработчиком:

// ПЛОХО - обработчик для каждого элемента (неэффективно)
const buttons = document.querySelectorAll('button');
buttons.forEach(button => {
  button.addEventListener('click', (e) => {
    console.log('Button clicked:', e.target.id);
  });
});

// При добавлении новой кнопки нужно добавить обработчик - сложно!
const newButton = document.createElement('button');
container.appendChild(newButton);
newButton.addEventListener('click', ...); // Снова!

// ХОРОШО - один обработчик на родителе (event delegation)
const container = document.getElementById('buttons-container');
container.addEventListener('click', (e) => {
  if (e.target.tagName === 'BUTTON') {
    console.log('Button clicked:', e.target.id);
  }
});

// Новые кнопки автоматически работают!
const newButton = document.createElement('button');
newButton.textContent = 'New Button';
container.appendChild(newButton); // Обработчик уже есть!

Real-world пример с динамическими элементами:

// Список товаров с кнопками удаления
const productList = document.getElementById('product-list');

// Один обработчик для всех кнопок (текущих и будущих)
productList.addEventListener('click', (e) => {
  if (e.target.classList.contains('delete-btn')) {
    const productId = e.target.closest('.product-item').dataset.id;
    deleteProduct(productId);
  }
  
  if (e.target.classList.contains('edit-btn')) {
    const productId = e.target.closest('.product-item').dataset.id;
    editProduct(productId);
  }
});

// Добавить новый товар
function addProduct(product) {
  const html = `
    <div class="product-item" data-id="${product.id}">
      <span>${product.name}</span>
      <button class="edit-btn">Редактировать</button>
      <button class="delete-btn">Удалить</button>
    </div>
  `;
  productList.insertAdjacentHTML('beforeend', html);
  // Кнопки сразу работают - обработчик уже на productList!
}

2. Общие операции для множественных элементов

// Обработка клика на любой элемент в таблице
const table = document.getElementById('users-table');

table.addEventListener('click', (e) => {
  const cell = e.target.closest('td');
  if (!cell) return; // Клик не на ячейку
  
  const row = cell.closest('tr');
  const userId = row.dataset.userId;
  
  if (e.target.tagName === 'A') {
    // Ссылка в ячейке
    e.preventDefault();
    viewUser(userId);
  }
  
  if (cell.classList.contains('editable')) {
    // Редактируемая ячейка
    makeEditable(cell);
  }
});

Control Flow: Останавливать всплытие

e.stopPropagation() - остановить всплытие

// Родитель
parent.addEventListener('click', (e) => {
  console.log('Parent clicked');
});

// Ребёнок со stopPropagation
button.addEventListener('click', (e) => {
  e.stopPropagation(); // Останавливаем всплытие
  console.log('Button clicked');
});

// Клик на button выведет:
// Button clicked
// (Parent clicked НЕ выведется!)

Когда использовать stopPropagation:

// 1. Модальное окно - не нужно обрабатывать клики за его границами
const modal = document.getElementById('modal');
modal.addEventListener('click', (e) => {
  e.stopPropagation(); // Клики внутри модала не влияют на остальное
});

// Фон модала
document.addEventListener('click', (e) => {
  closeModal(); // Вызовется при клике вне модала
});

// 2. Кнопка "Ещё" в списке - не нужно открывать строку при клике
list.addEventListener('click', (e) => {
  if (e.target.classList.contains('expand-btn')) {
    const item = e.target.closest('li');
    item.classList.toggle('expanded'); // Это хорошо
  }
});

list.addEventListener('click', (e) => {
  const item = e.target.closest('li');
  if (!e.target.classList.contains('expand-btn')) {
    selectItem(item); // Но это вызовется даже при клике на expand-btn!
  }
});

// ЛУЧШЕ:
button.addEventListener('click', (e) => {
  e.stopPropagation(); // Останавливаем всплытие
  expandItem();
});

e.preventDefault() vs e.stopPropagation()

Это РАЗНЫЕ вещи:

// preventDefault() - отменяет DEFAULT ACTION элемента
link.addEventListener('click', (e) => {
  e.preventDefault(); // Не следовать ссылке
  loadPageViaAjax(href); // Вместо этого загрузить AJAX
  // Всплытие ВСЕ ЕЩЕ происходит!
});

// stopPropagation() - останавливает всплытие события
button.addEventListener('click', (e) => {
  e.stopPropagation(); // Событие не поднимется дальше
  // Но default action всё ещё может произойти!
});

// Может потребоваться оба
button.addEventListener('click', (e) => {
  e.preventDefault(); // Отменить default action
  e.stopPropagation(); // И остановить всплытие
  handleClick();
});

Event Capture vs Bubbling

Фаза Capture (противоположна Bubbling)

// По умолчанию - Bubbling фаза (3-й параметр = false)
button.addEventListener('click', handler, false); // Bubbling

// Capture фаза - событие идёт вниз (3-й параметр = true)
parent.addEventListener('click', handler, true); // Capture

// Пример:
window.addEventListener('click', () => console.log('1. Window Capture'), true);
window.addEventListener('click', () => console.log('2. Window Bubble'), false);

document.addEventListener('click', () => console.log('3. Document Capture'), true);
document.addEventListener('click', () => console.log('4. Document Bubble'), false);

button.addEventListener('click', () => console.log('5. Button Target'));

// Клик на button выведет:
// 1. Window Capture (идёт вниз)
// 3. Document Capture (идёт вниз)
// 5. Button Target (цель)
// 4. Document Bubble (идёт вверх)
// 2. Window Bubble (идёт вверх)

React: Event Delegation уже встроена

React использует event delegation под капотом

function UserList({ users }) {
  const handleDelete = (userId: string) => {
    // React delegate события на корневой элемент
    // Поэтому работает эффективно
    deleteUser(userId);
  };
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => handleDelete(user.id)}>
            Удалить
          </button>
        </li>
      ))}
    </ul>
  );
}

// React:  все обработчики onClick делегированы на root element
// Это очень эффективно для больших списков!

Best Practices

// 1. Используй event delegation для динамических элементов
container.addEventListener('click', (e) => {
  const action = e.target.dataset.action;
  if (action === 'delete') {
    delete(e.target.dataset.id);
  }
});

// 2. Используй closest() для поиска нужного элемента
container.addEventListener('click', (e) => {
  const item = e.target.closest('[data-item-id]');
  if (item) {
    console.log('Item:', item.dataset.itemId);
  }
});

// 3. Проверяй classList или атрибуты
if (e.target.classList.contains('delete-button')) {
  // Это кнопка удаления
}

// 4. Используй stopPropagation() только когда необходимо
// stopPropagation() затрудняет отладку и может сломать другой код
button.addEventListener('click', (e) => {
  e.stopPropagation();
  // Хорошая причина
});

// 5. Удаляй обработчики когда элементы удаляются (утечки памяти)
const item = document.createElement('div');
item.addEventListener('click', handler);
// ...
item.removeEventListener('click', handler);
item.remove();

Performance: Event Delegation

Event Delegation намного эффективнее

// ПЛОХО - 1000 обработчиков для 1000 элементов
const items = document.querySelectorAll('.item');
items.forEach(item => {
  item.addEventListener('click', handleClick);
  // 1000 обработчиков! Много памяти, медленно добавлять/удалять
});

// ХОРОШО - 1 обработчик для 1000 элементов
const container = document.getElementById('items');
container.addEventListener('click', (e) => {
  if (e.target.classList.contains('item')) {
    handleClick(e);
    // 1 обработчик! Быстро, экономит память
  }
});

Итог

Event Bubbling нужно для:

  1. Event Delegation - один обработчик для множества элементов
  2. Эффективность - меньше обработчиков = меньше памяти
  3. Динамические элементы - новые элементы сразу работают
  4. Упрощение кода - меньше обработчиков = проще поддерживать

Всплытие — это не просто техническая деталь, а мощный инструмент для написания эффективного и гибкого JavaScript кода. Event delegation — один из самых важных паттернов в DOM программировании.