[Javascript] 이벤트 버블링과 캡쳐링

이벤트 버블링 (Event Bubbling)

  • 한 요소에 이벤트가 발생하면 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작하고 최상단의 부모 요소를 만날 때까지 반복되면서 핸들러가 동작하는 현상을 이벤트 버블링이라고 한다.
  • 즉, 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가는 특성이다.

하단의 코드를 보면 더욱 자세히 이해할 수 있다.

<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>

를 클릭하면 아래와 같은 결과가 실행된다.

three
two
one

div 태그 한 개만 클릭했을 뿐인데 3개의 이벤트가 발생되는 이유?

브라우저가 이벤트를 감지하는 방식 때문이다.
브라우저는 특정 화면 요소에서 이벤트가 발생했을 때 그 이벤트를 최상위에 있는 화면 요소까지 이벤트를 전파시킨다.
따라서, 클래스 three -> two -> one 순서로 div 태그에 등록된 이벤트들이 실행되는 것!
각 태그마다 이벤트가 등록되어 있기 때문에 상위 요소로 이벤트가 전달되는 것을 확인할 수 있다.
하지만, 이벤트가 특정 div 태그에만 달려 있다면 위와 같은 동작 결과는 확인할 수 없다.

이벤트 캡쳐링 (Event Capturing)

  • 이벤트 버블링과 반대 방향으로 진행되는 이벤트 전파 방식이다.
  • 특정 이벤트가 발생했을 때 최상위 요소인 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 태그

<div class="three"> </div>

를 클릭하면 아래와 같은 결과가 실행된다.

one
two
three

이벤트버블링을 막는 방법? e.stopPropagation()을 쓰자!

  • 클릭한 타깃의 이벤트만 발생하고 상위 요소로 이벤트가 전파되는 것을 막으려면 e.stopPropagation() 을 사용하면 된다.
  • 이벤트 버블링의 경우: 클릭한 요소의 이벤트만 발생시키고 상위 요소로 이벤트를 전달하는 것을 방해한다.
  • 이벤트 캡쳐링의 경우: 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 이벤트를 전달하지 않는다.

이벤트 위임 (Event Delegation)도 알고 가자.

  • 캡쳐링과 버블링을 이용한 것으로, 부모 안에 있는 자식 요소들에(여러 엘리먼트) 각각 이벤트 리스너를 추가하지 않고, 공통되는 부모에 이벤트 리스너를 등록하여 자식 요소의 이벤트를 제어하는 방식이다.

하단의 코드로 이해해보자.

<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');
	});
});

querySelectorAll() 를 이용해 화면에 존재하는 모든 인풋 박스 요소를 가져온 다음 각 인풋 박스의 요소에 클릭 이벤트 리스너를 추가한다.
-> 화면 실행 후 각 리스트 아이템의 인풋박스(체크박스)를 클릭하면 경고창이 표시된다.

새로운 리스트 아이템을 추가할 경우?

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 itemList = document.querySelector('.itemList');
itemList.addEventListener('click', function(event) {
	alert('clicked');
});

화면의 모든 인풋 박스에 일일이 이벤트 리스너를 추가하는 대신 인풋 박스의 상위 요소인 ul 태그, .itemList에 이벤트 리스너를 달아놓고 하위에서 발생한 클릭 이벤트를 감지한다(=이벤트 버블링).

해당 코드를 추가 후 실행시킬 경우, 각 아이템을 클릭할 때 마다 경고창(클릭 이벤트)이 알맞게 동작한다.

고로 리스트 아이템을 추가할 시 마다 클릭 이벤트를 다는 수고가 없어진다.

출처: 캡틴판교, yesdoing님 블로그

좋은 웹페이지 즐겨찾기