
html 요소에 이벤트가 발생했을 때, 어떠한 흐름으로 이벤트가 전파되는지 알아보자.
이벤트는 총 세 단계에 걸쳐 전파된다.
최상위 요소에서부터 이벤트가 발생한 대상인 타깃 요소를 만날 때까지 하위 요소로 이벤트가 전파된다.
이벤트가 타깃 요소에 도달하면 캡쳐링 단계가 종료된다. 아래의 예시 코드를 살펴보자.
🔽 practice.html
<!DOCTYPE html>
<html lang="ko">
<head>
</head>
<body>
body 영역
<main>
main 영역
<div>
div 영역
<p>
p 영역
<span>
span 영역
</span>
</p>
</div>
</main>
<script>
const $body = document.querySelector('body');
const $main = document.querySelector('main');
const $div = document.querySelector('div');
const $p = document.querySelector('p');
const $span = document.querySelector('span');
$body.addEventListener('click', function() {
console.log('body 태그');
}, true);
$main.addEventListener('click', function() {
console.log('main 태그');
}, true);
$div.addEventListener('click', function() {
console.log('div 태그');
}, true);
$p.addEventListener('click', function() {
console.log('p 태그');
}, true);
$span.addEventListener('click', function() {
console.log('span 태그');
}, true);
</script>
</body>
</html>
addEventListener()는 세 번째 인자로boolean타입의 데이터를 받는다.
true를 인자로 넣어준다면 캡쳐링 단계에서 이벤트 리스너가 실행된다.
반대로, false로 인자를 넣어준다면 버블링 단계에서 이벤트 리스너가 실행된다.
기본값은 false이다. 따라서 일반적으로 쓰이는 이벤트 리스너는 버블링 단계에서 실행되는 이벤트 리스너이다.
🔽practice.html 일부 코드
$body.addEventListener('click', function() {
console.log('body 태그');
}, true);
span 요소에 클릭 이벤트가 발생하면, DOM의 최상위 요소 document에서부터 타깃 요소까지 이벤트가 전파되는 캡쳐링 단계가 수행된다.
body 태그에 설정된 이벤트 리스너의 세 번째 인수가 true이다.
따라서 클릭 이벤트를 하위 요소로 전파하는 캡쳐링 단계에서 body 태그를 만나면, body 태그에 설정한 이벤트 리스너가 실행이 되어 'body 태그' 문장이 출력이 된다.

practice.html 파일을 보자. body와 body 하위 요소는 클릭 이벤트를 전파하는 캡쳐링 단계를 만나면 수행되는 이벤트 리스너가 설정되었다. 그에 따라, body를 포함한 body의 하위 요소로 클릭 이벤트가 전파됨에 따라 각 이벤트 리스너가 실행된다.
따라서 아래와 같은 결과가 출력된다.

이벤트가 타깃 요소에 도달한 상태로, 타깃 요소에 등록된 이벤트 리스너가 실행된다.

타깃 요소에 등록된 이벤트 리스너가 캡쳐링 리스너이든 버블링 리스너이든 관계없이 모든 이벤트 리스너가 실행된다.
⭐ 타깃 요소의 리스너는 옵션 값인 {capture: false}를 기준으로 캡쳐링 단계에 속하는 리스너인지 버블링 단계에 속하는 리스너인지 구분한다. 다만, 타깃 요소의 리스너는 타깃 단계에서 실행된다.
🔽 타깃 단계 예시 코드1
<button id="btn">Click Me</button>
<script>
const $btn = document.getElementById('btn');
// 캡처링 단계에서 이벤트 리스너
$btn.addEventListener('click', function() {
console.log('캡쳐링 리스너 실행');
}, true);
// 버블링 단계에서 이벤트 리스너
$btn.addEventListener('click', function() {
console.log('버블링 리스너 실행');
}, false);
</script>
위 코드에서는 클릭 이벤트가 발생하면, 타깃 단계에서 두 리스너(캡쳐링 리스너, 버블링 리스너) 모두 실행된다.
다시말해, 최상위 요소에서부터 전파된 클릭 이벤트가 타깃 요소를 만났을 때의 이벤트 리스너와 역으로 타깃 요소에서 최상위 요소로 전파될 클릭 이벤트가 타깃 요소를 만났을 때의 이벤트 리스너가 실행된다.
🔽 타깃 단계 예시 코드2
<button id="btn">Click Me</button>
<script>
const $btn = document.getElementById('btn');
// 버블링 단계에서 이벤트 리스너
$btn.addEventListener('click', function() {
console.log('버블링 리스너 실행');
}, false);
</script>
위와 같은 경우는 타깃 객체에 캡쳐링 단계에서의 이벤트 리스너를 등록하지 않았다. 따라서, 캡쳐링 단계는 단순히 이벤트가 전파만 이루어지는 과정이 된다.
타깃 요소에 설정한 이벤트 리스너의 실행이 끝난 후에 버블링 단계가 시작된다.
타깃 요소에서부터 다시 최상위 요소로 이벤트가 전파된다.
아래의 예시 코드를 살펴보자.
🔽 practice.html
<!DOCTYPE html>
<html lang="ko">
<head>
</head>
<body>
body 영역
<main>
main 영역
<div>
div 영역
<p>
p 영역
<span>
span 영역
</span>
</p>
</div>
</main>
<script>
const $body = document.querySelector('body');
const $main = document.querySelector('main');
const $div = document.querySelector('div');
const $p = document.querySelector('p');
const $span = document.querySelector('span');
$body.addEventListener('click', function() {
console.log('body 태그');
}, false);
$main.addEventListener('click', function() {
console.log('main 태그');
}, false);
$div.addEventListener('click', function() {
console.log('div 태그');
}, false);
$p.addEventListener('click', function() {
console.log('p 태그');
}, false);
$span.addEventListener('click', function() {
console.log('span 태그');
}, false);
</script>
</body>
</html>
🔽practice.html 일부 코드
$body.addEventListener('click', function() {
console.log('body 태그');
}, false);
span 요소에 클릭 이벤트가 발생했다고 하자. 타깃 요소에서 상위 요소로 클릭 이벤트를 전파하는 버블링 단계에서 body 태그를 만나면, body 태그에 설정한 이벤트 리스너가 실행이 되어 'body 태그' 문장이 출력이 된다.

타깃 요소의 상위 요소에 버블링 단계에서 실행되는 이벤트 리스너를 설정해줌으로써, 상위 요소로 클릭 이벤트가 전파되면서 각 이벤트 리스너가 실행된다.
따라서 아래와 같은 결과가 출력된다.

eventPhase속성은 이벤트 전파의 현재 단계를 나타낸다.
캡쳐링 단계 (값: 1)
최상위 요소에서 타깃 요소로 이벤트가 전파되고 있는 단계.
타깃 단계 (값: 2)
이벤트가 타깃 요소에 도달한 단계.
버블링 단계 (값: 3)
타깃 요소에서 최상위 요소로 이벤트가 다시 전파되는 단계.
🔽event.eventPhase 사용 예제
<!DOCTYPE html>
<html lang="ko">
<head>
</head>
<body>
body 영역
<main>
main 영역
<div>
div 영역
<p>
p 영역
<span>
span 영역
</span>
</p>
</div>
</main>
<script>
const $body = document.querySelector('body');
const $main = document.querySelector('main');
const $div = document.querySelector('div');
const $p = document.querySelector('p');
const $span = document.querySelector('span');
// 캡쳐링 단계
$body.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] body 태그');
}, true);
$main.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] main 태그');
}, true);
$div.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] div 태그');
}, true);
$p.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] p 태그');
}, true);
// 타깃 요소를 만났을 때
$span.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] span 태그');
}, true);
// 버블링 단계
$body.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] body 태그');
}, false);
$main.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] main 태그');
}, false);
$div.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] div 태그');
}, false);
$p.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] p 태그');
}, false);
// 타깃 요소를 만났을 때
$span.addEventListener('click', function(event) {
console.log('[' + event.eventPhase + '] span 태그');
}, false);
</script>
</body>
</html>

event객체의stopPropagation()Web APIs 메서드를 사용해서 이벤트 전파를 막을 수 있다.
실무에서도 자주 쓰이니 기억해둘 것!
🔽stopPropagation() 사용 예제
<!DOCTYPE html>
<html lang="ko">
<head>
</head>
<body>
body 영역
<main>
main 영역
<div>
div 영역
<p>
p 영역
<span>
span 영역
</span>
</p>
</div>
</main>
<script>
const $body = document.querySelector('body');
const $main = document.querySelector('main');
const $div = document.querySelector('div');
const $p = document.querySelector('p');
const $span = document.querySelector('span');
$body.addEventListener('click', function() {
console.log('body 태그');
}, true);
$main.addEventListener('click', function() {
console.log('main 태그');
}, true);
$div.addEventListener('click', function() {
console.log('div 태그');
}, true);
$p.addEventListener('click', function(event) {
event.stopPropagation();
console.log('p 태그');
}, true);
$span.addEventListener('click', function() {
console.log('span 태그');
}, true);
</script>
</body>
</html>
이벤트가 캡쳐링 되다가, p 태그에서 이벤트 전파를 막기 때문에 해당 위치를 기점으로 더 이상 이벤트가 전파되지 않는다.

event객체의preventDefault()Web APIs 메서드를 사용해서 html 요소에 대한 기본 동작을 실행하지 않도록 지정할 수 있다.
🔽preventDefault 메서드 사용 예제
<!DOCTYPE html>
<html lang="ko">
<head>
</head>
<body>
<a href="http://naver.com">네이버 이동</a>
<script>
const $a = document.querySelector('a');
$a.addEventListener('click', function(event) {
event.preventDefault();
})
</script>
</body>
</html>
본래는 a 요소를 클릭하면 네이버 페이지로 이동해야 하지만, html 요소에 대한 기본 동작을 실행하지 않도록 preventDefault() 메서드로 지정했기 때문에 페이지가 이동되지 않는다.
preventDefault() 메서드는 submit 기능을 막거나 a 태그와 같은 html 요소에 대한 기본 기능을 막을 때 자주 사용한다.
mdn 이벤트 버블링과 캡쳐: https://developer.mozilla.org/ko/docs/Learn_web_development/Core/Scripting/Events
이벤트 버블링&캡쳐링, 이벤트 전파 관련 강의: https://youtu.be/0jtalJxrxhs?si=wKZhq8smAtnKYMio