이 글은 'CAPTAIN PANGYO`님의 '이벤트 버블링, 이벤트 캡처 그리고 이벤트 위임까지' 포스팅을 이해하며 다시 정리한 글입니다. 문제가 될 시 삭제하도록 하겠습니다.
참조 링크: https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/
상위의 화면 요소란? HTML 요소는 기본적으로 트리 구조를 갖습니다. 여기서는 트리 구조상으로 한 단계 위에 있는 요소를 상위 요소라고 하며 body 태그를 최상위 요소라고 부를 예정이다.
<body>
<div class="one">
<div class="two">
<div class="three">
</div>
</div>
</div>
</body>
var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
div.addEventListener('click', logEvent);
});
function logEvent(event) {
console.log(event.currentTarget.className);
}
div
태그에 모두 클릭 이벤트를 등록하고 클릭 했을 때 logEvent
함수를 실행시키는 코드다. 여기서 위 그림대로 최하위 div
태그 <div class="three"></div>
를 클릭하면 아래와 같은 결과가 실행된다.분명
div
태그 하나만 클릭했는데 3개의 이벤트가 발생했다. 그 이유는 뭘까?
이는 브라우저가 특정 화면 요소에서 이벤트가 발생했을 때 그 이벤트를 최상위에 있는 화면 요소까지 이벤트를 전파시키기 때문이다.
이와 같이 하위에서 상위 요소로의 이벤트 전파 방식을 이벤트 버블링(Event Bubbling)이라고 한다.
그림과 같이 특정 이벤트가 발생했을 때 최상위 요소인
body
태그에서 이벤트가 발생한 태그를 찾아내려 한다. 어떻게 구현할 수 있을까?
<body>
<div class="one">
<div class="two">
<div class="three">
</div>
</div>
</div>
</body>
var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
div.addEventListener('click', logEvent, {
capture: true // default 값은 false입니다.
});
});
function logEvent(event) {
console.log(event.currentTarget.className);
}
addEventListener()
API에서 옵션 객체에 capture:true
를 설정해주면 된다. 그러면 해당 이벤트를 감지하기 위해 이벤트 버블링과 반대 방향으로 탐색합니다.
따라서, 아까와 동일하게 <div class="three"></div>
를 클릭하면 아래와 같이 거꾸로 결과가 나타나게 된다.
event.stopPropagation()
만약 원하는 화면 요소의 이벤트만 신경을 쓰고 싶다면 어떻게 해야할까?
stopPropagation()
웹 API이다.function logEvent(event) {
event.stopPropagation();
}
// 이벤트 버블링 예제
divs.forEach(function(div) {
div.addEventListener('click', logEvent);
});
function logEvent(event) {
event.stopPropagation();
console.log(event.currentTarget.className); // three
}
three
만 찍히는 것을 볼 수 있다. (이벤트가 상위로 전파되지 않음)// 이벤트 캡쳐 예제
divs.forEach(function(div) {
div.addEventListener('click', logEvent, {
capture: true // default 값은 false입니다.
});
});
function logEvent(event) {
event.stopPropagation();
console.log(event.currentTarget.className); // one
}
one
만 찍힌다.이벤트 위임이란, '하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식'을 의미한다.
<h1>오늘의 할 일</h1>
<ul class="itemList">
<li>
<input type="checkbox" id="item1">
<label for="item1">이벤트 버블링 학습</label>
</li>
<li>
<input type="checkbox" id="item2">
<label for="item2">이벤트 캡쳐 학습</label>
</li>
</ul>
var inputs = document.querySelectorAll('input');
inputs.forEach(function(input) {
input.addEventListener('click', function(event) {
alert('clicked');
});
});
// ...
// 새 리스트 아이템을 추가하는 코드
var itemList = document.querySelector('.itemList');
var li = document.createElement('li');
var input = document.createElement('input');
var label = document.createElement('label');
var labelText = document.createTextNode('이벤트 위임 학습');
input.setAttribute('type', 'checkbox');
input.setAttribute('id', 'item3');
label.setAttribute('for', 'item3');
label.appendChild(labelText);
li.appendChild(input);
li.appendChild(label);
itemList.appendChild(li);
새로 추가된 리스트 아이템(이벤트 위임 학습)에서 클릭 이벤트가 동작하지 않게 된다.
코드를 보면, 인풋 박스에 클릭 이벤트 리스너를 추가하는 시점에서 리스트 아이템은 두 개다. 따라서, 새롭게 추가된 리스트 아이템에는 클릭 이벤트 리스너가 등록되지 않았다.
그렇다면 이런 경우에는 매번 새롭게 추가된 리스트 아이템까지 클릭 이벤트 리스너를 일일이 달아줘야 할까?
// var inputs = document.querySelectorAll('input');
// inputs.forEach(function(input) {
// input.addEventListener('click', function() {
// alert('clicked');
// });
// });
var itemList = document.querySelector('.itemList');
itemList.addEventListener('click', function(event) {
alert('clicked');
});
// 새 리스트 아이템을 추가하는 코드
// ...
.itemList
에 이벤트 리스너를 달아놓고 하위에서 발생한 클릭 이벤트를 감지한다. 이 부분이 앞에서 봤던 이벤트 버블링이다. 코드를 바꾸고 실행하면 다시 정상적으로 동작하는 것을 확인할 수 있다.