특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 상위의 화면 요소들로 전달되어 가는 특성
<body>
<div class="one"> one
<div class="two"> two
<div class="three"> three
</div>
</div>
</div>
</body>
<script>
var divs = document.querySelectorAll('div');
divs.forEach(function(div) {
div.addEventListener('click', logEvent);
});
function logEvent(event) {
console.log(event.currentTarget.className);
}
</script>
위 코드는 중첩된 세 개의 div
코드에 모두 클릭 이벤트를 등록하고 클릭했을 때 logEvent
함수를 실행시키는 코드이다.
여기서 최하위 div
태그인 <div class="three">
를 클릭하면 위와 같이 결과가 실행된다.
위 코드를 도식화하면 아래와 같다.
div
태그 한 개만 클릭했을 뿐인데 왜 3개의 이벤트가 발생되는 이유는 브라우저가 이벤트를 감지하는 방식 때문이다.
브라우저는 특정 화면 요소에서 이벤트가 발생했을 때 그 이벤트를 최상위에 있는 화면 요소까지 전파시킨다.
따라서 클래스면 three → two → one 순서로 div
태그에 등록된 이벤트들이 실행된다.
마찬가지로 two 클래스를 갖는 두 번째 태그를 클릭했다면 two → one의 순으로 클릭 이벤트가 동작할 것이다.
여기서 주의할 점은 각 태그마다 클릭 이벤트가 등록되어 있기 때문에 상위 요소로 이벤트가 전달되는 것이다.
만약 이벤트가 특정 div
태그에만 달려있었다면 위와 같은 동작 결과는 확인할 수 없다.
이벤트 버블링과 반대방향으로 진행되는 이벤트 전파 방식
이벤트 캡처는 addEventListener()
에서 옵션 객체에 capture:true
를 설정하여 구현할 수 있다.
그러면 해당 이벤트를 감지하기 위해 이벤트 버블링과 반대 방향으로 탐색한다.
<body>
<div class="one"> one
<div class="two"> two
<div class="three"> three
</div>
</div>
</div>
</body>
<script>
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);
}
</script>
따라서 이벤트 버블링에서의 예시와 동일하게 <div class="three">
태그를 클릭해도 아래와 같은 결과가 나타난다.
stopPropagation()
은 해당 이벤트가 전파되는 것을 막는다.
따라서 위의 예제의 logEvent
함수에 stopPropagation()
API를 사용한다면 이벤트 버블링 예제에서는 three, 이벤트 캡처 예제에서는 one이 출력될것이다.
function logEvent(event) {
event.stopPropagation();
console.log(event.currentTarget.className); // three
}
하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소들의 이벤트를 제어하는 방식
<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) {
console.log('clicked');
});
});
할 일 목록을 간단한 리스트 아이템으로 나타낸 코드이다.
querySelectorAll()
을 이용해 화면에 존재하는 모든 input
요소를 가져온 다음 각 요소에 클릭 이벤트 리스너를 추가한다.
화면을 실행시키고 각 리스트 아이템의 체크 박스를 클릭하면 아래와 같이 핸들러가 동작한다.
그런데 만약 여기서 리스트 아이템을 더 추가한다면 매번 새롭게 추가된 리스트 아이템까지 클릭 이벤트 리스너를 일일이 달아줘야 한다.
// ...
// 새 리스트 아이템을 추가하는 코드
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');
// 이벤트 리스너 추가
input.addEventListener('click', function(event) {
console.log('clicked');
});
label.setAttribute('for', 'item3');
label.appendChild(labelText);
li.appendChild(input);
li.appendChild(label);
itemList.appendChild(li);
리스트 아이템이 많아지면 많아질수록 이벤트 리스너를 다는 작업 자체가 매우 번거로워지는데, 이를 해결할 수 있는 방법이 이벤트 위임이다.
위 코드를 아래와 같이 변경해보자
// ul태그에 이벤트 리스너 추가
var itemList = document.querySelector('.itemList');
itemList.addEventListener('click', function(event) {
console.log('clicked');
});
화면의 모든 input
요소에 이벤트 리스너를 추가하는 대신, input
요소들의 상위 요소인 ul
태그, .itemList
에 이벤트 리스너를 달아놓고 하위에서 발생한 클릭 이벤트를 감지한다. (이벤트 버블링)
결과는 아래와 같다.
Reference
https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/
https://ko.javascript.info/event-delegation