브라우저에서 이벤트가 발생하면 다음과 같이 세 단계로 동작한다.
이벤트 캡처링
: 이벤트가 하위 요소로 전파된다.이벤트 타깃
: 이벤트가 실제 타깃 요소에 전달된다.이벤트 버블링
: 이벤트가 상위 요소로 전파된다.
<td>
를 클릭했을 때 브라우저는 이벤트 타겟을 찾기 시작한다. 가장 먼저 실행되는 단계가 이벤트 캡쳐링이다.
브라우저의 최상단 객체인 window부터 시작해 DOM 트리의 하위 계층을 따라 내려가며 이벤트를 하위 요소로 전파한다.
이벤트 캡쳐링이 발생했을 때 이벤트 리스너는 이를 감지하지 않는다. 캡쳐링을 감지하기 위해서는 다음과 같이 addEventListener
의 capture 속성을 true로 지정해야 한다.
이렇게 지정하면 해당 이벤트 핸들러는 캡쳐링 단계에서 발생하고 버블링 단계에서 발생하지 않는다.
elem.addEventListener(..., {capture:true});
사실 이벤트 캡쳐링은 개발 과정에서 거의 쓰이지 않는다.
어떤 요소에 부착된 이벤트 핸들러가 해당 요소에 대한 정보를 가장 자세히 알고 있기 때문에 미리 상위 계층에서 해당 이벤트를 처리할 필요는 없기 때문이다.
따라서 <td>
에서 발생한 이벤트에 대해 처리할 권리를 해당 요소의 이벤트 핸들러에게 가장 먼저 부여하는 것이 바람직하다. 그 다음에서야 상위 계층으로 점차 올라가며 이벤트 핸들러를 실행시키는 것이 더 자연스럽다.
이벤트 캡쳐링으로 이벤트의 타겟인 <td>
에 도달했다면 이제 브라우저는 이벤트 버블링 단계를 실행한다.
타겟 요소의 이벤트 핸들러가 동작하고 이어 상위 계층의 핸들러가 동작한다. 브라우저 최상단 요소를 만날 때까지 각 요소에 부착된 핸들러를 실행한다.
타겟 요소에서 시작되어 점차 상위 계층으로 이벤트가 전파되는 것이 마치 물속의 거품과 닮아 이벤트 버블링이라고 한다.
아래 예시를 보자, alert 창이 어떤 순서로, 몇 번 띄워질까?
<div onclick="alert('DIV')">
<p onclick="alert('P')">
<button onclick="alert('BUTTON')">click me</button>
</p>
</div>
alert창은 BUTTON > P > DIV 순서대로 총 세 번 나타난다.
1. button에 부착된 핸들러 실행
2. p에 부착된 핸들러 실행
3. div에 부착된 핸들러 실행
대부분의 이벤트는 버블링이 되지만 focus
와 같은 몇 이벤트는 버블링이 되지 않는다는 점을 참고하자.
이벤트 핸들러는 이벤트에 대한 상세 정보를 담고 있다. 따라서 부모, 조상 ... 상위 요소에서 이벤트에 대한 정보에 접근할 수 있다.
이벤트가 발생한 요소는 event.target으로 접근할 수 있다.
event.target과 event.currentTarget의 차이는 다음과 같다.
[event.target]
이벤트가 발생한 타겟 요소다. 이벤트 버블링이 진행되어도 동일한 값을 유지한다.
[event.currentTarget]
현재 이벤트 핸들러를 실행중인 요소다. 이벤트 버블링이 진행됨에 따라 값이 변한다.
이벤트 버블링을 막기 위해서 이벤트 객체의 event.stopPropataion
메서드를 사용할 수 있다.
위와 똑같은 예시에 해당 메서드를 사용했을 때 alert 창은 하나도 뜨지 않는 것을 확인할 수 있다.
<div onclick="alert('DIV')">
<p onclick="alert('P')">
<button onclick="event.stopPropagation()">click me</button>
</p>
</div>
해당 메서드는 이벤트 버블링을 막아주지만, 다른 핸들러가 실행되는 것을 막진 못한다. 따라서 해당 요소에 부착된 모든 이벤트 핸들러의 동작을 막고 이벤트 버블링까지 중단시키려면 event.stopImmediatePropagation()
메서드를 사용하면 된다.
사실 아주 특별한 경우가 아니라면 이벤트 버블링을 막는 것은 지양하는 것이 좋다.
웹서비스에서 사용자의 행동 패턴을 분석하는 프로그램은 이벤트 감지를 통해 분석을 진행하는데, 이 때 event.stopPropagation
메서드가 있는 곳에서는 해당 이벤트를 감지할 수 없기 때문에 정확한 분석 결과를 도출해내지 못하는 경우가 발생할 수 있기 때문이다.
따라서 서비스의 특징과 아키텍쳐를 고려해 개발 과정에서 신중히 사용하는 것이 필요하다.