이벤트

셀라문·2022년 3월 14일
0

JavaScript

목록 보기
26/27

이벤트의 3단계
그렇다면 버블링과 캡쳐링이 함께 일어날 수도 있을까? 이벤트가 특정 엘리먼트(타겟 엘리먼트) 에 일어날 경우 해당 이벤트는 3단계를 거친다.

  • 1단계 - 캡쳐링 단계 : window 부터 타겟 엘리먼트까지 이벤트가 아래로 전달된다.
  • 2단계 - 타겟 단계 : 이벤트가 타겟 엘리먼트에 도달한다.
  • 3단계 - 버블링 단계 : 이벤트가 타겟 엘리먼트로부터 부모 엘리먼트들에게로 전달된다.

이벤트 버블링 - Event Bubbling

이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가는 특성을 의미합니다. 아래와 같은 그림처럼요.

<body>
	<div class="one">
		<div class="two">
			<div class="three">
			</div>
		</div>
	</div>
</body>
HTMLCopy
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 클래스를 갖는 div 태그를 클릭했을 때의 결과
div 태그 한 개만 클릭했을 뿐인데 왜 3개의 이벤트가 발생되는 걸까요? 그 이유는 브라우저가 이벤트를 감지하는 방식 때문입니다.

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

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

이와 같은 하위에서 상위 요소로의 이벤트 전파 방식을 이벤트 버블링(Event Bubbling)이라고 합니다.

이벤트 캡쳐 - Event Capture

이벤트 캡쳐는 이벤트 버블링과 반대 방향으로 진행되는 이벤트 전파 방식입니다.


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

위 그림처럼 특정 이벤트가 발생했을 때 최상위 요소인 body 태그에서 해당 태그를 찾아 내려갑니다. 그럼 이벤트 캡쳐는 코드로 어떻게 구현할 수 있을까요?

<body>
	<div class="one">
		<div class="two">
			<div class="three">
			</div>
		</div>
	</div>
</body>
HTMLCopy
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>를 클릭해도 아래와 같은 결과가 나타납니다.


three 클래스를 갖는 div 태그를 클릭했을 때의 결과

event.stopPropagation()

“난 이렇게 복잡한 이벤트 전달 방식 알고 싶지 않고, 그냥 원하는 화면 요소의 이벤트만 신경 쓰고 싶어요.”라고 생각하시는 분들이 충분히 있을 수 있습니다. 실제로 마감 기한에 쫓기는 상황에서 이런 동작 방식을 정확히 이해하는 시간보다는 구현에 더 많은 시간을 쏟아야 하기 때문입니다. 그럴 때는 아래처럼 stopPropagation() 웹 API를 사용합니다.

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

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

위와 같이 logEvent 함수에 stopPropagation() API를 사용한다면 앞의 ‘이벤트 버블링 예제’와 ‘이벤트 캡쳐 예제’에서 사용한 코드 기준으로 각각 three와 one이 찍히겠네요.

// 이벤트 버블링 예제
divs.forEach(function(div) {
	div.addEventListener('click', logEvent);
});

function logEvent(event) {
	event.stopPropagation();
	console.log(event.currentTarget.className); // three
}
// 이벤트 캡쳐 예제
divs.forEach(function(div) {
	div.addEventListener('click', logEvent, {
		capture: true // default 값은 false입니다.
	});
});

function logEvent(event) {
	event.stopPropagation();
	console.log(event.currentTarget.className); // one

이벤트 위임 (Event Delegation)

하나하나 이벤트를 등록하게 되면 for문을 쓰게되고 길어질수록 메모리를 많이 차지하여 이벤트 위임을 사용하는 것이 좋다.

이벤트 위임의 장점:

  • 다수의 이벤트 핸들러를 할당하는 대신에 하나의 이벤트 핸들러만 할당하기 때문에, 코드가 단순해지고 메모리가 절약된다.
  • 요소가 추가되거나 제거되는 동작이 많은 경우에도 짧은 코드만으로 이벤트 처리가 가능하다.

이벤트 위임의 단점:

  • 이벤트 위임을 사용하기 위해 이벤트가 반드시 버블링 되어야 한다.
    focus 이벤트처럼 버블링 되지 않는 이벤트나, stopPropagation()을 사용한 경우에는 이벤트 위임을 사용할 수 없다.

여러개의 자식 엘리먼트 이벤트 관리하기

이벤트 위임은 캡쳐링과 버블링을 이용한 것으로, 여러 엘리먼트마다 각각 이벤트 핸들러를 할당하지 않고, 공통되는 부모에 이벤트 핸들러를 할당하여 이벤트를 관리하는 방식이다

대표적으로 이벤트 위임이 어떻게 사용되는지 알아보도록 하겠다.

정해진 액션에 따라서 다른 동작을 하는 여러 버튼에 대한 이벤트는 어떻게 처리해야할까 ? 모든 버튼에 대해서 이벤트 리스너를 등록하면 매우 비효율 적일 것이다.

해결 방법은 이벤트 위임 방식을 이용하여 공통 부모에 이벤트를 등록하고, 정해진 data-action에 따라서 다른 함수를 실행하는 것이다.

<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>
const list = document.querySelector('ul');

list.addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    console.log(e.target.innerText);
  }
});

이벤트 위임에서는 위 코드처럼 각각의 <li> 태그에 일일이 이벤트 핸들러를 할당하는 대신에, <li>들의 공통 부모 요소인 <ul> 태그에 하나의 이벤트 핸들러를 할당합니다. 이벤트 버블링으로 인해 <li>에서 발생한 이벤트가 <ul>로 전파되기 때문에, <li>에서 발생한 클릭 이벤트를 상위 요소<li>에서 감지하고 처리할 수 있습니다. 위 코드의 경우 <li>를 클릭하면 클릭한<li>의 텍스트가 콘솔에 출력됩니다.

아래 예제는 서로 다른 역할을 하는 세 개의 버튼의 이벤트를 어떻게 이벤트 위임 방식으로 처리하는지 보여준다.

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Example</title>
</head>
<body>
    <div id="Menu">
        <button data-action="save">저장하기</button>
        <button data-action="reset">초기화 하기</button>
        <button data-action="load">불러오기</button>
    </div>
    <script src="app.js"></script>
</body>
</html>
const $Menu = document.getElementById('Menu')

const ActionFunctions = {
  save: () => alert('저장하기'),
  reset: () => alert('초기화하기'),
  load: () => alert('불러오기'),
}

$Menu.addEventListener('click', e => {
  const action = e.target.dataset.action
  if (action) {
    ActionFunctions[action]()
  }
})

참고

profile
취미로 하는 공부기록장

0개의 댓글