모던 Javascript 튜토리얼을 읽고 정리한 글입니다.
이벤트 버블링과 캡처링은 이벤트의 흐름입니다.
브라우저는 이벤트가 발생하면 발생된 요소를 찾습니다.
이벤트가 발생한 요소가 다른 요소들로 감싸져 있다면 브라우저는 어떤 요소에서 발생되었는지 한 번에 알 수 없습니다.
그래서 이벤트가 발생한 좌표에 있는 가장 얕은 요소와 가장 깊은 요소 사이의 모든 요소들을 확인합니다.
요소들에 이벤트 핸들러가 있다면 실행시키며 확인하는데, 이벤트 핸들러가 실행되는 순서에 따라 버블링과 캡처링 달리 불리게 됩니다.
이 과정을 활용하면 하나하나 이벤트 리스너를 달아줄 필요 없이 이벤트를 넣어주고 동작시키는 이벤트 위임을 할 수 있고, 이벤트 전파를 막아 원하는 이벤트만 실행할 수 있습니다.
또한 유저의 패턴을 분석하는 코드를 사용할 때도 버블링이 사용됩니다.
따라서 이벤트 전파는 필요시에만 사용을 권장합니다.
이미지 출처: 모던 Javascript 튜토리얼
이벤트 흐름은 세가지 단계가 있습니다.
(1) 캡처링(Capture Phase): 이벤트가 하위 요소로 전파되는 단계
(2) 타깃(Target Phase): 이벤트가 실제 타깃 요소에 전달되는 단계
(3) 버블링(Bubbling Phase): 이벤트가 상위 요소로 전파되는 단계
이미지에서 가장 깊은 요소인 <td>를 클릭하면 이벤트가 최상위 조상에서 시작해 아래로 전파되고(캡처링 단계), 이벤트가 타깃 요소에 도착해 실행된 후(타깃 단계), 다시 위로 전파됩니다(버블링 단계)
한 요소에 이벤트가 발생하면 해당 요소의 핸들러외에도 가장 최상단의 조상 요소의 핸들러까지 동작합니다.
제일 깊은 곳에 있는 요소의 이벤트에서 시작해 부모 요소를 거슬러 올라가며 발생하는 모양이 마치 물속 거품과 닮았기 때문에 붙여진 이름입니다.
이미지 출처: 모던 Javascript 튜토리얼
아래의 코드로 만들어진 요소에는 각각 onclick 이벤트가 있습니다.
body 아래 모든 요소는 파란 실선이 있는 박스로 되어 있습니다.
FORM이 적힌 요소를 클릭하면 form 알림이 실행됩니다.
DIV가 적힌 요소를 클릭하면 div 알림과 form 알림이 실행됩니다.
P가 적힌 요소를 클릭하면 p알림과 div 알림과 form 알림이 실행됩니다.
<!doctype html>
<body>
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
</body>
p 태그를 클릭하면 p알림만 실행되어야 할것 같습니다.
하지만 p 태그를 클릭하면 onclick="alert('p')" 핸들러가 동작하며 바깥의 챈들러들을 차례 차례 동작시킵니다.
이벤트 핸들러는 각 핸들러는 아래와 같은 event 객체의 프로퍼티에 접근할 수 있습니다.
1. event.target
2. event.currentTarget (=this)
3. event.eventPhase
아래 코드의 핸들러는 form 태그에 하나밖에 없지만 form 태그 내부에서 발생하는 클릭 이벤트는 버블링 되어서 실행됩니다.
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="example.css">
</head>
<body>
클릭하면 <code>event.target</code>과 <code>this</code>정보를 볼 수 있습니다.
<form id="form">FORM
<div>DIV
<p>P</p>
</div>
</form>
<script src="script.js"></script>
</body>
</html>
// script.js
form.onclick = function(event) {
event.target.style.backgroundColor = 'yellow';
setTimeout(() => {
alert("target = " + event.target.tagName + ", this=" + this.tagName);
event.target.style.backgroundColor = ''
}, 0);
};
event.target은 실제 이벤트가 발생한 가장 안쪽의 요소입니다.
만약 p요소를 클릭했다면 event.target은 p태그입니다.
event.currentTarget은 이벤트가 호출된 함수의 this입니다.
핸들러가 실제 할당된 요소이며 메서드의 this는 함수를 호출하는 객체이기 때문에 form이 됩니다.
이벤트 버블링은 타깃 이벤트에서부터 window 객체까지 실행됩니다.
그렇기 때문에 필요하지 않은 상위 핸들러를 호출하지 않고 싶다면 event.stopPropagation() 등의 메서드를 사용할 수 있습니다.
event.stopPropagation()는 이벤트가 상위 요소로 전파되지 않도록 막습니다.
하지만 하나의 요소에 2개 이상의 핸들러가 있다면 모든 핸들러에 이벤트가 전파됩니다.
Event.stopImmediatePropagation()는 상위 요소뿐만 아니라 같은 요소의 다른 이벤트에도 전파를 막습니다.
<!doctype html>
<body onclick="alert(`버블링은 여기까지 도달하지 못합니다.`)">
<button onclick="event.stopPropagation()">클릭해 주세요.</button>
</body>
버블링과 반대되는 이벤트 흐름입니다.
이벤트가 발생하면 가장 최상단의 조상 요소의 핸들러부터 이벤트가 발행한 요소까지 핸들러가 동작하게 됩니다.
addEventListener(type, listener, useCapture);메서드의 capture 옵션을 true로 입력하면 캡처링 단계에서 이벤트를 잡아낼 수 있습니다.
elem.addEventListener(..., {capture: true})
// or
elem.addEventListener(..., true)
아래의 코드는 document 아래의 모든 요소들이 캡처링과 버블링을 잡아낼 수 있는 이벤트 핸들러를 가지고 있습니다.
<!doctype html>
<body>
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<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>
</body>
P를 클릭하면 HTML -> BODY -> FORM -> DIV ->
DIV -> FORM -> BODY -> HTML 순으로 이벤트 핸들러가 동작합니다.