HTML 문서의 각 엘리먼트들은 태그 안의 태그가 위치하는 식으로 계층적으로 이루어짐을 볼 수 있다.
이러한 계층적 구조 특징 때문에 만일 HTML 요소에 이벤트가 발생할 경우 연쇄적 이벤트 흐름이 일어나게 된다.
이것을 이벤트 전파라고 한다.
전파 방향에 따라 버블링과 캡처링으로 구분한다.
버블링: 자식요소에서 발생한 이벤트가 바깥 부모 요소로 전파(= default)
캡처링 : 자식 요소에서 발생한 이벤트가 부모 요소로부터 안쪽 자식 요소까지 도달
이벤트 흐름의 3 단계
- 캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계
- 타깃 단계 : 이벤트가 실제 타깃 요소에 전달되는 단계
- 버블링 단계 : 이벤트가 상위 요소로 전파되는 단계
예를 들어,
- <td>를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파된다 (캡처링 단계)
- 이벤트가 타깃 요소에 도착해 리스너를 실행한다 (타깃 단계)
- 그리고 다시 상위로 이벤트를 전파한다 (버블링 단계)
이처럼 브라우저는 사용자로부터 이벤트가 발생하면 가장 상단의 요소부터 하위의 요소까지 내려오고 다시 거슬러 올라가는 식으로 이벤트를 전달하여 발생하도록 한다.
만일 타깃 요소까지 이벤트를 전파하는 과정에서 그의 부모, 조상에도 이벤트 리스너가 등록되어 있다면 실행되게 된다.
캡처링 이벤트 등록 방식
세 번째 인자에 true를 넣어주면 캡처링 단계에서 이벤트를 처리하겠다는 의미이다
기본 값은 false로, 버블링 단계에서 이벤트가 처리된다.
element.addEventListener('click', handler, { capture: true });
element.addEventListener('click', handler, true);
stopPropagation()
stopPropagation() 메서드를 호출하면 버블링 또는 캡처링 설정에 따라 상위, 하위로 가는 이벤트 전파를 막을 수 있다.
<div id="ancestor">
<div id="parent">
<div id="child">클릭!</div>
</div>
</div>
..
child.addEventListener("click", (e) => {
e.stopPropagation() // 이벤트 전파 방지
print('child')
})
만약 child를 클릭했다면
일반적인 전파 흐름으로는,
캡처링: ancestor → parent → child
타깃: child
버블링: child → parent → ancestor
하지만,
위 코드에서 child의 이벤트 핸들러가 실행되고 stopPropagation() 때문에 더 이상 버블링이 되지 않는다. =상위로 이번트가 전파되지 않는다. 따라서 parent, ancestor의 핸들러는 실행되지 않는다.
stopPropagation로 버블링을 막아놓은 영역에선 '죽은 영역(dead zone)'이 되어버리기 때문에 분석이 제대로 되지 않을 수 있다.
따라서 꼭 필요한 경우를 제외하곤 버블링을 막지 않는 것이 좋다.
이벤트 위임
이벤트 위임이란, 버블링을 활용해서 부모에 이벤트를 등록하고, 자식 요소의 이벤트까지 처리하는 기법이다.
이벤트 위임은 비슷한 방식으로 여러 요소를 다뤄야 할 때 사용된다.
이를 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있다.
예를 들어
<html>
<body>
<div class="menu">
<button class="items">첫번째 버튼</button>
<button class="items">두번째 버튼</button>
<button class="items">세번째 버튼</button>
</div>
<script>
for (const item of document.querySelectorAll(".items")) {
item.addEventListener("click", (e) => {
alert(`${e.target.innerText}입니다.`);
});
}
</script>
</body>
</html>
위와 같이 일일이 버튼에 핸들러를 달지 않고,
<html>
<body>
<div class="menu">
<button class="items">첫번째 버튼</button>
<button class="items">두번째 버튼</button>
<button class="items">세번째 버튼</button>
</div>
<script>
const menu = document.querySelector(".menu");
menu.addEventListener("click", (e) => {
alert(`${e.target.innerText}입니다.`);
});
</script>
</body>
</html>
.menu에 이벤트를 달아 자식에게 전달해 줄 수 있다.
event.target: 실제 클릭된 요소
event.currentTarget: 이벤트 핸들러가 등록된 요소
<td> <strong>Northwest</strong> ... </td>
만약 strong 태그를 클릭하게 되면 event.target은 strong에 해당하는 요소가 된다.
let td = event.target.closest('td');
따라서, 아래와 같이 closest() 을 활용해 찾아주면 된다.
이벤트 위임은 많은 핸들러를 할당하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약된다.
하지만, 이벤트 위임을 사용하기 위해선 반드시 버블링 되어야 한다. 몇몇 이벤트는 버블링이 되지 않으면 낮은 레벨에 할당한 핸들러에는 event.stopPropagation()을 사용할 수 없다.