본문 바로가기
2주차

JavaScript 이벤트 전파

by earlchive 2025. 4. 24.

안녕하세요 36기 YB 조성하입니다!

 

프로젝트를 진행하면서 이벤트가 의도치 않게 여러 번 실행된다던가,

예상치 못 한 요소에서 발생하는 문제점 다들 경험해보신 적 있으신가요?

 

저는 그런 상황을 맞닥뜨릴 때마다 항상 원인을 파악하느라 애를 먹었던 기억이 있는데요....

그래서 지난 2주차 세미나 자료에 심화 내용으로 자리 잡고 있었던 이벤트 전파에 대해 복습하고 좀 더 자세히 알아보고자 합니다!

 


1. 이벤트 객체

이벤트 객체란?

사용자 입력(onclick, onkeyup, onscroll 등)을 트리거로 발생한 이벤트 정보를 담은 객체

 

JavaScript에서 이벤트가 발생하면 이벤트 객체(Event Object)가 전달됩니다.

이벤트 객체는 이벤트가 발생했을 때 실행하는 이벤트 핸들러/리스너의 인수가 되고, 해당 이벤트의 정보를 저장한 프로퍼티와 이벤트의 흐름을 제어하는 메서드를 담고 있습니다.

 

즉 단순히 어떤 이벤트가 일어났는지를 설명해줄 뿐만 아니라, 해당 이벤트의 흐름과 기본 동작을 제어할 수 있는 기능도 가지고 있는 것입니다!

 


 

이벤트 객체의 주요 메서드

e.preventDefault()

요소의 기본 동작을 중단하는 함수

form.addEventListener('submit', (e) => {
  e.preventDefault();
  console.log('Form submitted via JS!');
});

 

  • <form> 태그의 submit 이벤트는 기본적으로 페이지를 새로고침하는데, 위 코드에서는 e.preventDefault()를 추가해 자동 새로고침 방지

 

e.preventPropagation()

이벤트 전파를 막아주는 함수

document.getElementById('child').addEventListener('click', (e) => {
  e.stopPropagation(); 
  console.log('Child clicked!');
});
  • 부모 요소로의 이벤트 전파 방지

 

그렇다면 이벤트가 전파된다는 것은 무슨 뜻일까요?!


2. 이벤트 전파

이벤트 전파란?

HTML 문서는 트리 구조로 되어 있고, 이러한 계층적 특징 때문에 HTML 요소에 이벤트가 발생할 경우 그 구조에 따라 이벤트도 연쇄적인 흐름을 가지게 됩니다.

 

이러한 현상을 이벤트 전파(Event Propagation)라고 부르며, 전파 방향에 따라 버블링 캡처링으로 구분합니다.

 

 

🔵 이벤트 버블링

이벤트가 하위 요소에서 상위 요소로 올라가는 단계

 

🔵 이벤트 캡처링

이벤트가 최상위 요소에서 시작해서 이벤트 타깃까지 내려오는 단계

 


이벤트 전파의 흐름

🔵캡처링 → 🔵 타겟  →  🔵버블링
  1. 캡처링 단계 (capturing phase) : 이벤트가 하위 요소로 전파
  2. 타겟 단계 (target phase) : 이벤트가 실제 타깃 요소에 전달
  3. 버블링 단계 (bubbling phase) : 이벤트가 상위 요소로 전파

 

 

예시와 함께 이해해봅시다!

<html>
 <head><head/>
 <body>
  <table>
   <tbody>
    <tr>
     <td>Shady Grove</td>
     <td>Aeolian</td>
    </tr>
    <tr>
     <td>Over the River, Charlie</td> <!-- 클릭 이벤트를 여기에 등록한다면? -->
     <td>Dorian</td>
    </tr>
   </tbody>
  </table>
 </body>
</html>

 

1. <td>를 클릭하면 이벤트가  최상위 부모에서 시작해 아래로 전파 (캡처링 단계)

2. 이벤트가 타깃 요소에 도착해 리스너 실행 (타깃 단계)

3. 다시 상위로 이벤트 전파 (버블링 단계)

 


 

대부분의 이벤트 핸들러는 버블링 단계에서 실행됩니다.

element.addEventListener('click', handler);
// 기본값: capture = false
  • 등록한 핸들러는 이벤트가 타겟 요소에 도달한 후 버블링 단계에서 실행

capture: true를 사용하면 캡처링 단계에서 실행할 수도 있습니다.

element.addEventListener('click', handler, { capture: true });

 

 

예시와 함께 본다면!

parent.addEventListener('click', () => console.log('Parent'), true);  
// 캡처링
child.addEventListener('click', () => console.log('Child'));          
// 타깃
parent.addEventListener('click', () => console.log('Parent again'));  
// 버블링

 

>> 콘솔에 찍힌 결과는? 

Parent 
Child 
Parent again

 

 

 


왜 이벤트 전파를 막아야 할까요?

앞서 공부한 이벤트 전파의 영향으로 이벤트는 단순히 지정된 요소에서만 발생하지 않습니다.

DOM 트리를 따라 퍼져나가기 때문에, 이 흐름을 제어하거나 끊어줘야 의도한 동작만 정확히 실행될 수 있겠죠?

 

그래서 우리는 e.stopPropagation() 같은 메서드를 사용하고, 이를 통해 불필요한 이벤트 중복 실행이나 UI 오작동을 줄일 수 있어요!

 

 

e.stopPropagation() 사용해보기

<div class="modal-background">
 <div class="modal-content"> 
  <button class="close-btn">닫기</button> 
 </div>
</div>

 

배경을 클릭하면 모달이 닫히게 하고, 닫기 버튼을 클릭하면 모달만 닫히고 배경 클릭 이벤트는 실행되지 않게 하고 싶다면?

document.querySelector('.modal-background').addEventListener('click', () => { 
	closeModal(); // 모달 닫기 
}); 

document.querySelector('.close-btn').addEventListener('click', (e) => { 
	e.stopPropagation(); // 배경까지 이벤트 전파 차단
	closeModal(); // 모달 닫기 
});
  • 사용자가 닫기 버튼을 클릭하면 이벤트가 .close-btn → .modal-background로 버블링될 수 있음
  • 배경의 click 리스너까지 도달하면 closeModal() 재호출 

위와 같은 문제를 e.stopPropagation()으로 이벤트가 배경까지 전파되지 않도록 차단해줍니다.

 


+) 알아두면 유용한 것들

🔵 stopPropagation() vs. stopImmediatePropagation()

stopPropagation()은 부모로의 전파만 막고, 같은 요소 내 다른 리스너는 실행됩니다.

 

같은 요소에 여러 개 이벤트 리스너가 있을 경우 그 리스너들까지 모두 막으려면, stopImmediatePropagation() 메서드를 사용해보세요!

btn.addEventListener('click', (e) => { 
	console.log('첫 번째 리스너'); 
	e.stopImmediatePropagation(); 
}); 

btn.addEventListener('click', () => { 
	console.log('두 번째 리스너'); // 실행 X
});
 
 

🔵 이벤트 전파 흐름 시각화

개발자 도구에서 addEventListener의 useCapture 여부 Event.path (또는 composedPath)를 활용하면 이벤트 전파 경로를 시각화해볼 수 있어요!

element.addEventListener('click', (e) => { console.log(e.composedPath()); });​
 
 

3. 이벤트 위임

이벤트 위임이란?

하위 요소에 일일이 이벤트를 붙이지 않고, 상위 요소 하나에만 이벤트를 붙여 하위 요소의 이벤트를 제어하는 방식
<ul id="menu">
  <li>홈</li>
  <li>소개</li>
  <li>연락처</li>
</ul>
document.getElementById('menu').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    console.log(`${e.target.innerText} 클릭됨`);
  }
});
  • <li>를 클릭하면 이벤트가 <li> → #menu 순으로 버블링
  • #menu에서 이벤트 감지
  • e.target을 통해 실제 클릭된 요소가 <li>인지 확인 후 console.log() 동작 실행

이렇게 menu 요소에만 이벤트 리스너를 할당했지만, 실제로는 그 안에 있는 항목들을 클릭했을 때만 로그가 찍히도록 조건을 걸어줄 수 있습니다!


이벤트 위임, 왜 사용할까요?

  1. 동적으로 추가되는 요소까지 이벤트 처리 가능
  2. 메모리 누수는 줄이고, 성능은 개선됨
  3. 유지보수 간편

이벤트 위임은 이벤트 전파의 특성을 잘 활용한 방식으로, 부모 요소에 이벤트를 등록해 자식 요소의 이벤트들을 한꺼번에 처리할 수 있게 해줍니다!

 

이벤트 위임의 한계와 주의할 점

  1. mouseenter, mouseleave는 버블링되지 않아서 위임 불가능
  2. 위임할 요소가 너무 커지면 리스너 내부 조건이 복잡해져, 오히려 유지보수 어려워짐
  3. e.target e.currentTarget 차이로 인한 혼동 주의
parent.addEventListener('click', (e) => { 
	console.log('target:', e.target); 
	console.log('currentTarget:', e.currentTarget); 
});

 

target: 실제로 클릭된 요소
currentTarget : 이벤트를 감지한 요소

 

 


✨ 마무리하며

 

오늘은 이렇게 이벤트 전파에 대해 알아보았는데요, 이번 아티클 준비 과정을 통해 이제는 이벤트가 예상과 다르게 작동할 때 당황하지 않을 용기를... 얻고 갑니다

 

관련 개념들을 완전히 이해하고 미리 잘 짚어두면 앞으로의 개발 과정에서도 큰 도움이 될 것 같아요!

 

읽어주셔서 감사합니다 :)