ex)
<div onclick="alert('div에 할당한 핸들러!')">
<em><code>EM</code>을 클릭했는데도 <code>DIV</code>에 할당한 핸들러가 동작합니다.</em>
</div>
위 예시코드에 이벤트는 div에 할당되어 있지만, em이나 code 태그를 클릭해도 동작한다.
왜 동작하는 걸까??
버블링(bubbling)의 원리는
한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작한다. 가장 최상단의 조상요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작한다.
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
가장 안쪽 p태그를 클릭하면
1. p태그에 할당된 onclick 핸들러가 동작.
2. 바깥 div태그에 할당된 핸들러가 동작.
3. form 태그에 할당된 핸들러가 동작.
최상위인 document 객체를 만날 때까지, 각 요소에 할당된 onclick 핸들러가 동작한다.
이런 동작 방식 때문에 p 태그를 클릭하면 p -> div -> form 순서로 3개의 alert 창이 뜨게 된다.
이런 흐름을 '이벤트 버블링'이라고 한다. 가장 깊은 곳에 있는 요소에서 시작해 부모 요소를 거슬러 올라가는 모습이 물속 거품과 닮았기 때문이다.
우리가 흔히 사용하던 event.target은 정확히 이벤트가 어디서 발생했는지 등에 대한 자세한 정보를 얻을 수 있다.
이벤트가 발생한 가장 안쪽의 요소는 타겟(target)요소라고 하며, event target을 사용해 접근할 수 있다.
event.target과 this(event.currentTarget)은 다르다
this는 현재 요소로, 실행중인 핸들러가 할당된 요소를 참조한다.
이벤트 버블리은 타겟에서 시작해서 document 객체를 만날 때까지 각 노드에서 모두 발생한다. 몇몇 이벤트는 window 객체까지 거슬러 올라가기도 한다. 이때 모든 핸들러가 호출되는데 핸들러에게 이벤트를 완전히 처리하고 난 후 버블링을 중단하도록 명령할 수도 있다.
event.stopPropagation() 을 사용하면 된다.
<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)">
<button onclick="event.stopPropagation()">클릭해 주세요.</button>
</body>
event.stopPropagation()은 위쪽으로 일어나는 버블링은 막아주지만, 다른 핸들러들이 동작하는 건 막지 못한다.
버블링을 멈추고, 요소에 할당된 다른 핸들러의 동작도 막으려면 event.stopImmediatePropagation()을 사용해야 한다. 이 메서드를 사용하면 요소에 할당된 특정 이벤트를 처리하는 핸들러 모두가 동작하지 않는다.
버블링과는 정반대의 단계로 자주 쓰이진 않지만, 종종 유용한 경우가 있다고 한다.
캡쳐링 단계에서 이벤트를 실행하려면 addEventListener의 capture 옵션을 true로 설정해야 한다.
elem.addEventListener(..., {capture: true})
// 아니면, 아래 같이 {capture: true} 대신, true를 써줘도 됩니다.
elem.addEventListener(..., true)
capture 옵션은 두가지 값을 갖는다.
이벤트가 발생하면 이벤트가 발생한 가장 안쪽 요소가 event.target이 된다.
이벤트는 document에서 시작해 DOM트리를 따라 event.target까지 내려간다. 이벤트는 트리를 따라 내려가면서 addEventListener(..., true)로 할당한 핸들러를 동작시킨다. addEventListener(..., true)의 true는 {capture: true}의 축약형이다.
event.target에 설정된 핸들러가 호출된다.
이벤트가 event.target 부터 시작해서 최상위 노드까지 전달되면서 각 요소에 on로 할당한 핸들러와 addEventListener로 할당한 헨들러를 동작시킨다.
버블링을 멈출 수 있는 방법으로는 event.stopPropagation()이 있지만 추천되는 방법은 아니다. 추후에 버블링이 필요한 경우가 생기기 때문.