본문 바로가기

Frontend/javascript

이벤트 버블링(Event Bubbling)과 캡처링(Event Capture)

이벤트 등록

웹 어플리케이션에서 사용자의 입력을 받기 위해 필요한 기능이다. 대표적으로 addEventListener()는 웹 개발자들이 화면에 동적인 기능을 추가하기 위해 자연스럽게 접하게 디는 기본적인 기능이다.

// 이벤트 등록
<button>add one item</button>

<script>
    var button = document.querySelector('button');
    button.addEventListener('click', addItem);

    function addItem(event) {
        console.log(event);
    }
</script>

 

이벤트 버블링(Event Bubbling)

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

이벤트 버블링

// 이벤트 버블링
<body>
	<div class="one">
		<div class="two">
			<div class="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 class="three"></div>를 클릭하여 이벤트를 발생시키면 아래와 같은 결과가 콘솔창에 찍힌다.

한 개의 div 태그만 클릭했는데 왜 3개의 이벤트가 발생할까? 그 이유는 브라우저가 이벤트를 감지하는 방식 때문이다. 브라우저는 특정 화면 요소에서 이벤트가 발생했을 때 그 이벤트를 최상위에 있는 화면 요소까지 이벤트를 전파시킨다. 여기서 주의할 점은 각 태그마다 이벤트가 등록되어 있기 때문에 상위 요소로 이벤트가 전달되는 것이며, 만약 이벤트가 특정 div 태그에만 걸려 있다면 위와 같은 동작 결과는 확인할 수 없다.

 

이벤트 캡처링(Event Capture)

이벤트 캡처링은 이벤트 버블링과 반대 방향인 하위 요소로 진행되는 이벤트 전파 방식이다.

이벤트 캡처링

// 이벤트 캡처링
<body>
	<div class="one">
		<div class="two">
			<div class="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>

addEventListener() API에서 옵션 객체에 capture:true를 설정해주면, 해당 이벤트를 감지하기 위해 이벤트 버블링과 반대 방향으로 탐색합니다. 마찬가지로 위이 코드를 실행하고 <div class="three"></div>를 클릭하여 이벤트를 발생시키면 아래와 같은 결과가 콘솔창에 찍힌다.

 

event.target

부모의 요소의 핸들러는 이벤트가 정확히 어디서 발생했는지 등에 대한 자세한 정보를 얻을 수 있다. 이벤트가 발생한 가장 안쪽의 요소는 target 요소라고 불리고, event.target을 사용해 접근할 수 있다. 이때 event.target은 실제 이벤트가 시작된 ‘타깃’ 요소이며, 버블링이 진행되어도 변하지 않는다. 반면 this(=event.target)은 ‘현재’ 요소로, 현재 실행 중인 핸들러가 할당된 요소를 참조하는 차이점이 있다.

 

이벤트 전파 막기

이벤트 전파를 막기 위해서는 이벤트 객체 매서드인 event.stopPropagation()를 사용하면 된다.

더보기

event.stopImmediatePropagation()

한 요소의 특정 이벤트를 처리하는 핸들러가 여러개인 상황에서, 핸들러 중 하나가 버블링을 멈추더라도 나머지 핸들러는 여전히 동작합니다.

event.stopPropagation()은 위쪽으로 일어나는 버블링은 막아주지만, 다른 핸들러들이 동작하는 건 막지 못합니다.

버블링을 멈추고, 요소에 할당된 다른 핸들러의 동작도 막으려면 event.stopImmediatePropagation()을 사용해야 합니다. 이 메서드를 사용하면 요소에 할당된 특정 이벤트를 처리하는 핸들러 모두가 동작하지 않습니다.

더보기

꼭 필요한 경우를 제외하곤 버블링을 막지 마세요!

버블링은 유용합니다. 버블링을 꼭 멈춰야 하는 명백한 상황이 아니라면 버블링을 막지 마세요. 아키텍처를 잘 고려해 진짜 막아야 하는 상황에서만 버블링을 막으세요.

event.stopPropagation()은 추후에 문제가 될 수 있는 상황을 만들어낼 수 있습니다.

문제가 발생할만한 시나리오를 살펴봅시다.

  1. 중첩 메뉴를 만들었다 가정합시다. 각 서브메뉴(submenu)에 해당하는 요소에서 클릭 이벤트를 처리하도록 하고, 상위 메뉴의 클릭 이벤트 핸들러는 동작하지 않도록 stopPropagation을 적용합니다.
  2. 사람들이 페이지에서 어디를 클릭했는지 등의 행동 패턴을 분석하기 위해, window내에서 발생하는 클릭 이벤트 전부를 감지하기로 결정합니다. 일부 분석 시스템은 그렇게 분석합니다. 이런 분석 시스템의 코드는 클릭 이벤트를 감지하기 위해 document.addEventListener('click'…)을 사용합니다.
  3. stopPropagation로 버블링을 막아놓은 영역에선 분석 시스템의 코드가 동작하지 않기 때문에, 분석이 제대로 되지 않습니다. 안타깝게도 stopPropagation을 사용한 영역은 '죽은 영역(dead zone)'이 되어버립니다.

이벤트 버블링을 막아야 하는 경우는 거의 없습니다. 버블링을 막아야 해결되는 문제라면 커스텀 이벤트 등을 사용해 문제를 해결할 수 있습니다. 커스텀 이벤트 사용 방법은 추후에 다루겠습니다. 핸들러의 event 객체에 데이터를 저장해 다른 핸들러에서 읽을 수 있게 하면, 아래쪽에서 무슨 일이 일어나는지를 부모 요소의 핸들러에게 전달할 수 있으므로, 이 방법으로도 이벤트 버블링을 통제할 수 있습니다.

 

이벤트 위임 (Event Delegation)

이벤트 위임은 하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식이다. 예를 들어 이벤트가 위임되는 시점의 요소 개수와 이벤트 실행 시점의 요소 개수가 달라 위임 시점에 없었던 요소에 이벤트가 위임되지 않는다면 원하는 요소에 이벤트가 작동되지 않을 수 있다. 이러한 상황을 막기 위해 상위 요소에 이벤트를 부여하여 이벤트를 제어할 수도 있다.

 

https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/

 

이벤트 버블링, 이벤트 캡처 그리고 이벤트 위임까지

(기본) 이벤트 버블링, 이벤트 캡처링, 그리고 이벤트 위임까지 이벤트 전달 방식과 관련된 모든 것을 파헤쳐 봅니다.

joshua1988.github.io

 

https://ko.javascript.info/bubbling-and-capturing

 

버블링과 캡처링

 

ko.javascript.info

 

728x90

'Frontend > javascript' 카테고리의 다른 글

모던 자바스크립트 Deep Dive 스터디  (0) 2022.12.16
promise.all()  (0) 2022.08.01
onselect 이벤트  (0) 2022.02.10