이벤트엔 버블링 이외에도 '캡처링(capturing)' 이라는 흐름이 존재합니다. 실제 코드에서 자주 쓰이진 않지만, 종종 유용한 경우가 있습니다.
표준 DOM 이벤트에서 정의한 이벤트 흐름엔 3가지 단계가 있습니다.
테이블 안의 <td>
를 클릭하면 어떻게 이벤트가 흐르는지 아래 그림을 보고 이해해 봅시다.
*<td>
를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파되고(캡처링 단계),*
이벤트가 타깃 요소에 도착해 실행된 후(타깃 단계), 다시 위로 전파됩니다.(버블링 단계).
이런 과정을 통해 요소에 할당된 이벤트 핸들러가 호출됩니다.
캡처링 단계를 이용해야 하는 경우는 흔치 않기 때문에, 이전까지 주로 버블링만 설명하였습니다. 캡처링에 관한 코드를 발견하는 일은 거의 없을 겁니다.
on<event> 프로퍼티나 HTML 속성, addEventListener(event, handler) 를 이용해 할당된 핸들러는 캡처링에 대해 전혀 알 수 없습니다. 이 핸들러들은 두 번쨰 혹인 세 번쨰 단계의 이벤트 흐름(타깃 단계와 버블링 단계)에서만 동작합니다.
캡처링 단계에서 이벤트를 잡아내려면 addEventListener의 capture옵션을 true 로 설정해야 합니다.
element.addEventListener(..., {capture: true})
// 아니면, 아래 같이
element.addEventListener(..., true)
capture 옵션은 두 가지 값을 가질 수 있습니다.
공식적으론 총 3개의 이벤트 흐름이 있지만, 이벤트가 실제 타깃 요소에 전달되는 단계인 '타깃 단계'는 별도로 처리되지 않습니다. 캡처링과 버블링 단계의 핸들러는 타깃 단계에서 트리거됩니다.
<form>FORM
<div>DIV
<p>P</p>
</div>
</form>
<script>
for(let elem of document.querySelectorAll('*')) {
elem.addEventListener("click", e => alert(`캡쳐링: ${elem.tagName}`), true);
elem.addEventListener("click", e => alert(`버블링: ${elem.tagName}`));
}
</script>
이 예시는 문서 내 요소 '전체'에 핸들러를 할당해서 어떤 핸들러가 동작하는지를 보여줍니다.
<p>를 클릭하면 다름과 같은 순서로 이벤트가 전달됩니다.
*html
→ body
→ form
→ div
(캡처링 단계, 첫 번째 리스너)**p
(타깃 단계, 캡처링과 버블링 둘 다에 리스너를 설정했기 때문에 두 번 호출됩니다.)**div
→ form
→ body
→ html
(버블링 단계, 두 번째 리스너)**event.eventPhase
프로퍼티를 이용하면 현재 발생 중인 이벤트 흐름의 단계를 알 수 있습니다.*
반환되는 정숫값에 따라 이벤트 흐름의 현재 실행 단계를 구분할 수 있습니다. 하지만 핸들러를 통해 흐름 단계를 알 수 있기 때문에 이 프로퍼티는 자주 사용되지 않습니다.
핸들러를 제거할 때 removeEventListener()
같은 단계에 있어야 합니다.
addEventListener(..., true) 로 핸들러를 할당해 줬다면, 핸들러를 지울 때,
removeEventListener(..., true) 를 사용해 지워야 합니다. 같은 단계에 있어야 핸들러가 지워집니다.
★ 버블링과 캡처링은 '이벤트 위임(Event delegation)'의 토대가 됩니다. 이벤트 위임은 강력한 이벤트 핸들링 패턴입니다.