[JavaScript] 이벤트 버블링, 캡쳐링, 위임

SOLEE_DEV·2021년 1월 16일
5

Javascript

목록 보기
3/19
post-thumbnail

과거 프론트엔드 면접을 볼 당시 이벤트 버블링, 캡쳐링에 대한 대답을 하지 못했던 기억이 생각나 작성하게 된 포스팅.. 관련 자료를 찾던 중 역시나 캡틴 판교님께서 작성해주신 포스팅이 있어 작성하게 되었다! 자세한 자료는 해당 링크를 참고하세요!

1. 이벤트 버블링 - Event Bubbling

특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가는 특성을 의미한다.

하위의 클릭 이벤트가 상위로 전달되어 가는 그림

상위의 화면 요소란?
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개의 이벤트가 발생되는 이유는 브라우저가 이벤트를 감지하는 방식 때문이다.

브라우저는 특정 화면 요소에서 이벤트가 발생했을 때 그 이벤트를 최상위에 있는 화면 요소까지 이벤트를 전파시킨다. 따라서, 클래스 명 three -> two -> one 순서로 div 태그에 등록된 이벤트들이 실행된다. 마찬가지로 two 클래스를 갖는 두 번째 태그를 클릭했다면 tow -> one 순으로 클릭 이벤트가 동작한다.

여기서 주의해야 할 점은 각 태그마다 이벤트가 등록되어 있기 때문에 상위 요소로 이벤트가 전달되는 것을 확인할 수 있다. 만약 이벤트가 특정 div 태그에만 달려 있다면 위와 같은 동작 결과는 확인할 수 없다.

이와 같은 하위에서 상위 요소로의 이벤트 전파 방식을 이벤트 버블링이라고 한다.

2. 이벤트 캡쳐 - Event Capture

이벤트 캡쳐는 이벤트 버블링과 반대 방향으로 진행되는 이벤트 전파 방식이다.
(이벤트 버블링을 반대 방향으로 탐색하고 싶을 때 활용? 이벤트 리스너를 등록할 때 capture : true를 설정해주면 됨!)

클릭 이벤트가 발생한 지점을 찾아내려 가는 그림

위 그림처럼 특정 이벤트가 발생했을 때 최상위 요소인 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>를 클릭해도 아래와 같은 결과가 나타난다.

3. event.stopPropagation()

function logEvent(event) {
	event.stopPropagation();
}

위 API는 해당 이벤트가 전파되는 것을 막는다. 따라서, 이벤트 버블링의 경우에는 클릭한 요소의 이벤트만 발생시키고 상위 요소로 이벤트를 전달하는 것을 방해한다.
그리고 이벤트 캡쳐의 경우에는 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 이벤트를 전달하지 않는다.
(이벤트 버블링 : 클릭한 요소의 이벤트만 발생 / 이벤트 캡쳐링 : 최상위 요소만 동작)

4. 이벤트 위임 - 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()를 이용해 화면에 존재하는 모든 인풋 박스 요소를 가져온 다음 각 인풋 박스의 요소에 클릭 이벤트 리스너를 추가한다.

화면을 실행시키고 각 리스트 아이템의 인풋 박스를 클릭하면 'clicked' alert가 표시된다.

그런데 여기서 리스트 아이템을 추가한다면?

// 새 리스트 아이템을 추가하는 코드
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);

새로 추가된 리스트 아이템에는 클릭 이벤트 리스너가 동작하지 않는다.

코드를 다시 살펴보면, 인풋 박스에 클릭 이벤트 리스너를 추가하는 시점에서 리스트 아이템은 두 개이다. 따라서, 새롭게 추가된 리스트 아이템에는 클릭 이벤트 리스너가 등록되지 않았다.

이런 식으로 매번 새롭게 추가된 리스트 아이템까지 클릭 이벤트 리스너를 일일이 달아줘야 할까?
리스트 아이템이 많아지면 많아질수록 이벤트 리스너를 다는 작업 자체가 매우 번거롭다.

이 번거로운 작업을 해결할 수 있는 방법이 바로 이벤트 위임(Event Delegation)이다.

var itemList = document.querySelector('.itemList');
itemList.addEventListener('click', function(event) {
	alert('clicked');
});

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

이렇게 하게 된다면, 리스트 아이템을 새로 추가할 때마다 클릭 이벤트를 달지 않아도 된다! (신기)

위 코드는 현재 인풋 박스의 이벤트만 다루는 것이 아닌 label 태그의 이벤트도 감지한다. event 객체를 이용해 인풋 박스의 이벤트만 감지할 수 있도록 구현해보자!

5. 예제

profile
Front-End Developer

0개의 댓글