본문 바로가기
Dev/JavaScript

[JavaScript] 이벤트 다루기 - 버블링, 캡처링, 위임, debounce, throttle

by 5kdk 2022. 11. 27.

✔ Intro

이벤트 다루기 버블링, 캡처링, 위임, 디바운싱, 쓰로틀링에 대해 살펴본다.

 

계층적 구조에 포함되어 있는 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어난다. 즉, 이벤트가 전파(Event Propagation)되는데 전파 방향에 따라 버블링(Event Bubbling)과 캡처링(Event Capturing)으로 구분할 수 있다.

 

출저 : www.w3.org/TR/DOM-Level-3-Events

 addEventListener 메소드의 세번째 매개변수에 true를 설정하면 캡처링으로 전파되는 이벤트를 캐치하고 false 또는 미설정하면 버블링으로 전파되는 이벤트를 캐치한다.

 

 

👀 표준 DOM 이벤트에서 정의한 이벤트 흐름엔 3가지 단계

 

  • 캡처링 단계 – 이벤트가 하위 요소로 전파되는 단계
  • 타깃 단계 – 이벤트가 실제 타깃 요소에 전달되는 단계
  • 버블링 단계 – 이벤트가 상위 요소로 전파되는 단계

 

 

 

👀 addEventListener 메서드 방식

addEventListener 메서드

addEventListener 메소드를 이용하여 대상 DOM 요소에 이벤트를 바인딩하고 해당 이벤트가 발생했을 때 실행될 콜백 함수(이벤트 핸들러)를 지정한다.

 

  • 하나의 이벤트에 대해 하나 이상의 이벤트 핸들러를 추가할 수 있다
  • 캡처링과 버블링을 지원한다
  • HTML 요소뿐만아니라 모든 DOM 요소(HTML, XML, SVG)에 대해 동작한다. 브라우저는 웹 문서(HTML, XML, SVG)를 로드한 후, 파싱하여 DOM을 생성한다

 

 

✔ 이벤트 버블링

한 요소에 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작한다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작한다.

 

💡 exam

<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>Event Bubbling</title>
		<meta charset="utf-8" />
		<style>
			body {
				width: 100%;
				height: 100vh;
				display: flex;
				justify-content: center;
				align-items: center;
				flex-direction: column;
			}
			body * {
				margin: 20px;
				border: 2px solid red;
			}
			body * > * {
				border-color: orange;
			}
			body * > * > * {
				border-color: yellow;
			}
			body * > * > * > * {
				border-color: green;
			}
		</style>
	</head>
	<body>
		<article>
			ARTICLE
			<div>
				DIV
				<p>
					P
					<br />
					<button>BUTTON</button>
				</p>
			</div>
		</article>
	</body>
	<script>
		const article = document.querySelector('article');
		const division = document.querySelector('div');
		const paragraph = document.querySelector('p');
		const button = document.querySelector('button');

		article.addEventListener('click', () => window.alert('article'));

		division.addEventListener('click', () => {
			event.stopPropagation();

			window.alert('division');
		});

		paragraph.addEventListener('click', () => window.alert('paragraph'));

		button.addEventListener('click', (event) => {
			event.stopPropagation();

			window.alert('button');
		});
	</script>
</html>

 

 

✔ 이벤트 캡처링

이벤트엔 버블링 이외에도 ‘캡처링(capturing)’ 이라는 흐름이 존재한다. 실제 코드에서 자주 쓰이진 않지만, 종종 유용한 경우가 있다.

 

💡 exam

<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>Event Capturing</title>
		<meta charset="utf-8" />
		<style>
			body {
				width: 100%;
				height: 100vh;
				display: flex;
				justify-content: center;
				align-items: center;
				flex-direction: column;
			}
			body * {
				margin: 20px;
				border: 2px solid red;
			}
			body * > * {
				border-color: orange;
			}
			body * > * > * {
				border-color: yellow;
			}
			body * > * > * > * {
				border-color: green;
			}
		</style>
	</head>
	<body>
		<div>
			<h1>장바구니</h1>
			<ul>
				<li>노트북</li>
				<li>모니터</li>
				<li>책상</li>
				<li>의자</li>
				<li>키보드</li>
				<li>마우스</li>
			</ul>
		</div>
	</body>
	<script>
		const container = document.querySelector('div');
		const list = document.querySelector('ul');

		container.addEventListener('click', (event) => {
			console.error('DIV에 이벤트 발생');
			event.target.style.background = 'blue';
		});

		list.addEventListener('click', (event) => {
			console.warn('UL 이벤트 발생');
			event.target.style.background = 'red';
		});
	</script>
</html>

 

 

✔ 이벤트 위임

캡처링과 버블링을 활용하면 강력한 이벤트 핸들링 패턴인 이벤트 위임(event delegation) 을 구현할 수 있다.
이벤트 위임은 비슷한 방식으로 여러 요소를 다뤄야 할 때 사용된다. 이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있게 된다.
공통 조상에 할당한 핸들러에서 event.target을 이용하면 실제 어디서 이벤트가 발생했는지 알 수 있고, 이를 이용해 이벤트를 핸들링한다.

 

💡 exam

<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>Event Delegation</title>
		<meta charset="utf-8" />
		<style>
			body {
				width: 100%;
				height: 100vh;
				display: flex;
				justify-content: center;
				align-items: center;
				flex-direction: column;
			}
			body * {
				margin: 20px;
				border: 2px solid red;
			}
			body * > * {
				border-color: orange;
			}
		</style>
	</head>
	<body>
		<h1>장바구니</h1>
		<ul>
			<li class="item">노트북</li>
			<li class="item">모니터</li>
			<li class="item">책상</li>
			<li class="item">의자</li>
			<li class="item">키보드</li>
			<li class="item">마우스</li>
			<li class="item aaa bbb ccc">마우스</li>
		</ul>
	</body>
	<script>
		const items = document.querySelector('ul');

		items.addEventListener('click', (event) => {
			// const item = event.target.classList[0];
			const [item] = event.target.classList;

			console.dir(event.target.classList);

			// if (event.target.nodeName !== 'LI') {
			// 	return;
			// }
			if (item === 'item') {
				event.target.style.background = 'blue';
			}
		});
	</script>
</html>

 

 

✔ 이벤트 디바운싱

연속으로 호출되는 함수(이벤트)에서 가장 마지막(혹은 처음) 이벤트만 실행되도록 한다. 즉, 입력(이벤트)가 끝난 후에 요청을 보내면 된다. (input 에서 많이 사용된다)

이벤트 발생 시점 -> 입력이 끝날 때까지 무한정 대기
이벤트 발생 시(timeout) 타이머를 설정해두고, 설정 시간 이전에 이벤트(함수 호출)가 발생하면 이전 타이머는 클리어, 새로운 타이머를 설정해준다. 설정 시간 이전에 이벤트가 발생하지 않으면 이벤트 발생을 끝내고 코드가 실행된다.

 

 

💡 exam

<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>Debounce</title>
		<meta charset="utf-8" />
		<style>
			body {
				width: 100%;
				height: 100vh;
				display: flex;
				justify-content: center;
				align-items: center;
				flex-direction: column;
			}
			body * {
				margin: 20px;
				border: 2px solid red;
			}
			body * > * {
				border-color: orange;
			}
		</style>
	</head>
	<body>
		<form>
			<input type="text" />
		</form>
	</body>
	<script>
		let i = 0;

		document.querySelector('input').addEventListener(
			'keyup',
			debounce(() => {
				i = i + 1;
				console.log(i);
			}, 500),
		);
		/**
		 *
		 * 이벤트 발생이 많을 때 가장 마지막 이벤트만을 실행시킨다.
		 *
		 * 실행시킬 함수, 지연시킬 밀리세컨드
		 * */
		function debounce(callback, wait) {
			let timeout; // 초기 undefined
			
			// 함수가 호출시 매번 반환
			return function (...args) {
				const context = this;

				// 타이머를 취소시켜준다.
				clearTimeout(timeout);
				timeout = setTimeout(() => callback.apply(context, args), wait);
			};
		}
	</script>
</html>

 

 

✔ 이벤트 쓰로틀링

연속으로 호출되는 함수(이벤트)를 일정 시간 동안에 한번씩만 실행되도록 한다. 즉 몇 초, 몇 밀리초에 한번씩만 실행되게 제한을 두는 것. 스크롤 이벤트에서 많이 사용한다.  

이벤트 발생 시점 -> 입력이 시작되면 일정 주기로 반복 실행.
이벤트 발생 시 특정 시간에 한번씩 실행되도록 함수를 설정해준다. 이벤트가 발생하면 설정 시간에 한 번 씩만 이벤트가 실행된다.

 

💡 exam

<!DOCTYPE html>
<html lang="ko">
	<head>
		<title>Throttle</title>
		<meta charset="utf-8" />
		<style>
			body {
				width: 100%;
				display: flex;
				justify-content: center;
				align-items: center;
				flex-direction: column;
				overflow: scroll;
			}

			div {
				border: 1px solid black;
				width: 500px;
				height: 600px;
			}
		</style>
	</head>

	<body>
		<div style="background-color: red"><h2>1</h2></div>
		<div style="background-color: orange"><h2>2</h2></div>
		<div style="background-color: yellow"><h2>3</h2></div>
		<div style="background-color: green"><h2>4</h2></div>
		<div style="background-color: blue"><h2>5</h2></div>
		<div style="background-color: navy"><h2>6</h2></div>
		<div style="background-color: violet"><h2>7</h2></div>
	</body>
	<script>
		let i = 0;

		window.addEventListener(
			'scroll',
			throttle(() => {
				i = i + 1;
				console.log(i);
			}, 1000),
		);

		/**
		 * 이벤트 발생이 많을 때 특정 밀리세컨드동안 이벤트를 차단하고 단 한번만 실행시킨다
		 *
		 * 실행시킬 함수, 차단시킬 밀리세컨드
		 * */
		function throttle(callback, wait) {
			let timeout = null;

			return function (...args) {
				const context = this;

				if (!timeout) {
					timeout = setTimeout(() => {
						callback.apply(this, args);
						timeout = null;
					}, wait);
				}
			};
		}
	</script>
</html>

 

 

참고자료

https://developer.mozilla.org/ko/docs/Learn/JavaScript/Building_blocks/Events

https://poiemaweb.com/js-event

 

댓글