Contents
see ListJavaScript 이벤트 전파 (Event Propagation)
JavaScript에서 이벤트가 DOM 트리를 따라 전파되는 방식을 이해하면 복잡한 UI 상호작용을 효과적으로 처리할 수 있습니다. 캡처링, 버블링, 이벤트 위임 패턴을 알아봅니다.
언제 사용하나요?
- 동적으로 생성되는 요소에 이벤트 처리
- 부모-자식 요소 간 이벤트 충돌 해결
- 이벤트 위임으로 성능 최적화
- 모달, 드롭다운 외부 클릭 감지
이벤트 전파 3단계
1. 캡처링 (Capturing) - 위에서 아래로
window → document → html → body → ... → target
2. 타겟 (Target) - 실제 이벤트 발생 요소
3. 버블링 (Bubbling) - 아래에서 위로
target → ... → body → html → document → window
대부분의 이벤트는 버블링됨 (focus, blur, scroll 제외)
기본 예시
<div id="outer">
외부
<div id="inner">
내부
<button id="btn">클릭</button>
</div>
</div>
<script>
// 버블링 (기본)
document.getElementById("outer").addEventListener("click", () => {
console.log("outer clicked");
});
document.getElementById("inner").addEventListener("click", () => {
console.log("inner clicked");
});
document.getElementById("btn").addEventListener("click", () => {
console.log("button clicked");
});
// 버튼 클릭 시 출력:
// button clicked
// inner clicked
// outer clicked
</script>
캡처링 사용
// 세 번째 인자를 true로 설정
document.getElementById("outer").addEventListener("click", () => {
console.log("outer (capture)");
}, true); // 캡처링 단계에서 실행
document.getElementById("inner").addEventListener("click", () => {
console.log("inner (bubble)");
}, false); // 버블링 단계에서 실행 (기본값)
// 버튼 클릭 시:
// outer (capture) - 캡처링
// button clicked - 타겟
// inner (bubble) - 버블링
전파 중단
// stopPropagation() - 전파 중단
document.getElementById("btn").addEventListener("click", (e) => {
e.stopPropagation(); // 버블링 중단
console.log("button clicked");
});
// 버튼 클릭 시 "button clicked"만 출력
// stopImmediatePropagation() - 같은 요소의 다른 핸들러도 중단
document.getElementById("btn").addEventListener("click", (e) => {
e.stopImmediatePropagation();
console.log("first handler");
});
document.getElementById("btn").addEventListener("click", () => {
console.log("second handler"); // 실행 안됨
});
이벤트 위임 (Event Delegation)
<ul id="list">
<li data-id="1">항목 1</li>
<li data-id="2">항목 2</li>
<li data-id="3">항목 3</li>
</ul>
<script>
// 나쁜 예: 각 li에 이벤트 등록
document.querySelectorAll("li").forEach(li => {
li.addEventListener("click", handleClick);
});
// 좋은 예: 부모에 위임
document.getElementById("list").addEventListener("click", (e) => {
// 클릭된 요소 확인
if (e.target.tagName === "LI") {
const id = e.target.dataset.id;
console.log("선택된 ID:", id);
}
});
// 장점:
// 1. 이벤트 핸들러 수 감소 (메모리 절약)
// 2. 동적으로 추가된 요소도 자동 처리
// 3. DOM 조작 후 재등록 불필요
</script>
closest()로 이벤트 위임 개선
<div class="card-list">
<div class="card" data-id="1">
<h3>제목</h3>
<p>내용</p>
<button class="delete-btn">삭제</button>
</div>
</div>
<script>
document.querySelector(".card-list").addEventListener("click", (e) => {
// 삭제 버튼 클릭 확인
const deleteBtn = e.target.closest(".delete-btn");
if (deleteBtn) {
const card = deleteBtn.closest(".card");
const id = card.dataset.id;
deleteCard(id);
return;
}
// 카드 클릭 확인
const card = e.target.closest(".card");
if (card) {
openCard(card.dataset.id);
}
});
</script>
외부 클릭 감지 (모달 닫기)
<div class="modal-overlay">
<div class="modal">
<h2>모달 제목</h2>
<p>내용</p>
</div>
</div>
<script>
document.querySelector(".modal-overlay").addEventListener("click", (e) => {
// 모달 외부(오버레이) 클릭 시 닫기
if (e.target.classList.contains("modal-overlay")) {
closeModal();
}
});
// 또는 모달 내부 클릭 전파 중단
document.querySelector(".modal").addEventListener("click", (e) => {
e.stopPropagation();
});
</script>
기본 동작 막기
// preventDefault() - 기본 동작 방지
document.querySelector("a").addEventListener("click", (e) => {
e.preventDefault(); // 링크 이동 방지
console.log("링크 클릭됨");
});
document.querySelector("form").addEventListener("submit", (e) => {
e.preventDefault(); // 폼 제출 방지
// 커스텀 제출 로직
});
이벤트 객체 주요 속성
element.addEventListener("click", (e) => {
e.target; // 실제 클릭된 요소
e.currentTarget; // 이벤트가 등록된 요소
e.type; // 이벤트 타입 ("click")
e.bubbles; // 버블링 여부
e.eventPhase; // 1:캡처, 2:타겟, 3:버블
});