HTML 요소에서 이벤트가 발생하면 해당 요소를 포함한 모든 조상 요소에 이벤트를 전달한다. 왜 전달하는지 알아보고 이벤트가 전파되는 과정을 설명한다.
Window 객체나 XMLHttpRequest 객체처럼 단독으로 존재하는 객체에서 이벤트가 발생하면, 브라우저는 해당 객체에 등록된 이벤트 처리기를 호출한다. 반면, 이벤트가 HTML 요소에서 발생하면 해당 요소와 조상 요소에 등록된 모든 이벤트 처리기가 호출된다. 자식 요소를 클릭하면, 컴퓨터는 자식 요소를 클릭한 건지, 부모 요소를 클릭한 건지 모르기 때문이다.
→ 그러니까 p 태그에 클릭 이벤트가 설정돼 있으면, p 태그를 클릭해도 div, form 태그에 할당된 이벤트 핸들러가 실행이 된다는 뜻.
→ 그래서 브라우저는 p부터 차근차근 부모의 부모 요소인 form 태그까지 올라갔다가 다시 p태그로 내려오면서, 해당 이벤트에 반응하는 이벤트 처리기나 이벤트 리스너가 등록돼 있는 요소가 있는지 확인한다. 발견하면 기억해뒀다가 차례로 그 이벤트를 실행한다.
<form onclick="alert('form')">
form
<div onclick="alert('div')">
div
<p onclick="alert('p1')">p1</p>
<p onclick="alert('p2')">p2</p>
</div>
</form>
p1 태그를 클릭하면 window 에서부터 p1 태그까지 요소 중간에 있는 이벤트들을 전부 검색해서 따로 메모리에 가지고 있다가, 해당 요소부터 다시 차례대로 위로 올라가면서 이벤트들을 발생시킨다. 해당 코드의 경우 다음과 같이 클릭 이벤트에 반응한다.
이 순서가 디폴트지만 항상 이런 순서인 것은 아니다. 캡쳐링 단계 또는 버블링 단계에서 실행되도록 타깃에 이벤트를 설정할 수 있다. 바닐라JS에서 이벤트리스너를 등록할 때 쓰이는 addEventListener를 이용하여 캡쳐링과 타깃, 버블링에 대해 알아보자.
→ 이벤트가 Window 객체에서 출발하여 DOM 트리를 타고 이벤트 타깃까지 전파되는 현상
→ 캡쳐링 단계에 반응하도록 등록된 이벤트 리스너는 이벤트 타깃에 등록된 이벤트 처리기나 이벤트 리스너보다 먼저 실행됨
→ 이벤트가 이벤트 타깃에 전파되는 단계
→ 이벤트 타깃에 등록된 이벤트 처리기나 이벤트 리스너는 이 시점에 실행됨
→ 이벤트가 타깃에서 시작하여 DOM 트리를 타고 Window 객체까지 전파되는 현상, 기본적으로 버블링은 항상 발생함
→ 버블링 단계에 반응하도록 등록된 이벤트 리스너는 타깃에 등록된 이벤트 처리기나 이벤트 리스너보다 먼저 실행됨
→ 타깃에 이벤트 처리기가 등록된 상태라면 기본적으로 이벤트 처리기는 타깃 단계와 버블링 단계에 모두 실행됨
addEventListener의 세 번째 인자, useCapture가 디폴트로 false 값인 상태면 타깃 단계, 버블링 단계에서 실행. true면 캡쳐링 단계와 타깃 단계에서 실행됨.
<body>
<div id="outer">
outer
<p id="inner1">inner1</div>
<p id="inner2">inner2</div>
</div>
</body>
<script>
window.onload = function() {
var outer = document.getElementById("outer");
var p2 = document.getElementById("inner1");
outer.addEventListener("click", function(e) {
console.log("outer bubbling");
}, false);
outer.addEventListener("click", function(e) {
console.log("outer capturing");
}, true);
inner1.addEventListener("click", function(e) {
console.log("inner1 bubbling");
}, false);
}
</script>
→ addEventListener 세 번째 인자 주목: true면 ①, ②번에서 실행, false면 ②, ③에서 실행
→ ①, ②, ③ 순으로 실행되므로 콘솔에는 "outer capturing" → "inner1 bubbling" → "outer bubbling" 순으로 찍힘
원하는 타깃에만 이벤트를 발생시키고 싶을 때, 이벤트 전파를 취소해서 부모 요소가 이벤트 처리를 하지 않도록 할 수 있다. 또 이벤트 발생 시 기본적으로 실행되는 브라우저의 동작도 취소할 수 있다.
inner1.addEventListener("click", function(e) {
console.log("inner1 bubbling");
e.stopPropagation()
}, false);
inner1.addEventListener("click", function(e) {
console.log("inner1 bubbling");
e.stopImmediatePropagation()
}, false);
inner1.addEventListener("click", function(e) {
if (!confirm("페이지를 이동하시겠습니까?")) e.preventDefault()
}, false);