버블링의 원리는 간단합니다.
한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작합니다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할달된 핸들러가 동작합니다.
이벤트가 제일 깊은 곳에 있는 요소에서 시작해 부모 요소를 거슬러 올라가며 발생하는 모양이 마치 물속 거품과 닮았기 때문입니다.
거의 모든 이벤트는 버블링 됩니다.
키워드는 '거의' 입니다.
focus
이벤트와 같이 버블링 되지 않는 이벤트도 있습니다. 버블링 되지 않는 이벤트의 종류에 대해선 조금 후에 알아보겠습니다. 몇몇 이벤트를 제외하곤 대부분의 이벤트는 버블링 됩니다.
부모 요소의 핸들러는 이벤트가 정확히 어디서 발생했는지 등에 대한 자세한 정보를 얻을 수 있습니다.
이벤트가 발생한 가장 안쪽의 요소는 타깃(target) 요소라고 불리고, event.target
을 사용해 접근할 수 있습니다.
event.target
과 this
(=event.currentTarget
)는 다음과 같은 차이점이 있습니다.
event.target
은 실제 이벤트가 시작된 '타깃' 요소입니다. 버블링이 진행되어도 변하지 않습니다.this
는 '현재' 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조합니다.이벤트 버블링은 타깃 이벤트에서 시작해서 <html>
요소를 거쳐 document
객체를 만날 때까지 각 노드에서 모두 발생합니다. 몇몇 이벤트는 window
객체까지 거슬러 올라가기도 합니다. 이 때도 모든 핸들러가 호출됩니다.
그런데 핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 명령할 수도 있습니다.
이벤트 객체의 메서드인 event.stopPropagation()
를 사용하면 됩니다.
event.stopImmediatePropagation()
한 요소의 특정 이벤트를 처리하는 핸들러가 여러개인 상황에서, 핸들러 중 하나가 버블링을 멈추더라도 나머지 핸들러는 여전히 동작합니다.
event.stopPropagation()
은 위쪽으로 일어나는 버블링은 막아주지만, 다른 핸들러들이 동작하는 건 막지 못합니다.
버블링을 멈추고, 요소에 할당된 다른 핸들러의 동작도 막으려면event.stopImmediatePropagation()
을 사용해야 합니다. 이 메서드를 사용하면 요소에 할당된 특정 이벤트를 처리하는 핸들러 모두가 동작하지 않습니다.
이벤트엔 버블링 외에도 '캡처링'이라는 흐름이 존재합니다. 실제 코드에서 자주 쓰이진 않지만, 종종 유용한 경우가 있으므로 알아봅시다.
표준 DOM 이벤트에서 정의한 이벤트 흐름엔 3가지 단계가 있습니다.
1. 캡처링 단계 - 이벤트가 하위 요소로 전파되는 단계
2. 타깃 단계 - 이벤트가 실제 타깃 요소에 전달되는 단계
3. 버블링 단계 - 이벤트가 상위 요소로 전파되는 단계
테이블 안의 <td>
를 클릭하면 어떻게 이벤트가 흐르는지 아래 그림을 보고 이해해 봅시다.
<td>
를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파되고(캡처링 단계), 이벤트가 타깃 요소에 도착해 실행된 후(타깃 단계), 다시 위로 전파됩니다(버블링 단계). 이런 과정을 통해 요소에 할당된 이벤트 핸들러가 호출됩니다.
캡처링 단계를 이용해야 하는 경우는 흔치 않기 때문에, 이전까지 주로 버블링만 설명했습니다. 캡처링에 관한 코드를 발견하는 일은 거의 없을 겁니다.
on<event>
프로퍼티나 HTML 속성, addEventListner(event, handler)
를 이용해 할당된 핸들러는 캡처링에 대해 전혀 알 수 없습니다. 이 핸들러들은 두 번째 혹은 세 번째 단계의 이벤트 흐름(타깃 단계와 버블링 단계)에서만 동작합니다.
캡처링 단계에서 이벤트를 잡아내려면 addEventListener
의 capture
옵션을 true
로 설정해야 합니다.
elem.addEventListener(..., {capture: true})
elem.addEventListener(..., true)