이벤트 전파, 이벤트 위임

chaeruru·2021년 10월 4일
1

이벤트 전파

DOM 트리 상에 존재하는 DOM 요소 노드에서 발생한 이벤트는 DOM 트리를 통해 전파되는데 이것을 이벤트 전파라 한다.

생성된 이벤트 객체는 이벤트를 발생시킨 DOM 요소인 이벤트 타깃을 중심으로 DOM 트리를 통해 전파된다.

이벤트 전파는 이벤트 객체가 전파되는 방향에 따라 다음과 같이 3단계로 구분할 수 있다.

  • 캡처링 단계: 이벤트가 상위 요소에서 하위 요소 방향으로 전파
  • 타깃 단계: 이벤트가 이벤트 타깃에 도달
  • 버블링 단계: 이벤트가 하위 요소에서 상위 요소 방향으로 전파

이벤트 버블링

특정 요소에 이벤트가 발생하면 그 요소부터 가장 최상단의 조상 요소를 만날 때까지 상위 요소로 올라가면서 해당 이벤트가 전달되는 특성을 말한다.

버블링 예제

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <div class="one">
      One
      <div class="two">
        Two
        <div class="three">Three</div>
      </div>
    </div>
    <script>
      const $divs = document.querySelectorAll('div');
      $divs.forEach(div =>
        div.addEventListener('click', e => console.log(e.currentTarget.className))
      );
    </script>
  </body>
</html>

위의 예제에서 Three를 클릭하면 다음과 같이 three two one 순으로 출력이 된다.

자세히 살펴보면 <div class="three">Three</div> 를 클릭하면 클릭 이벤트가 발생하여 클릭 이벤트 객체가 생성되고 <div class="three">Three</div> 요소가 이벤트 타깃이 된다.

이때 클릭 이벤트 객체는 window에서 시작해서 이벤트 타깃 방향으로 전파되는데 이것이 캡처링 단계이다.

이후 이벤트 객체는 이벤트를 발생시킨 이벤트 타깃에 도달한다. 이것이 타깃 단계이다.

이후 이벤트 객체는 이벤트 타깃에서 시작해서 window 방향으로 전파된다. 이것이 버블링 단계이다.

즉 요악하면

window → ... → div.one → div.two → div.three 순으로 캡처링

div.three → div.two → div.one → ... → window 순으로 버블링이 발생하였고

현재 이벤트 핸들러는 버블링 단계의 이벤트를 캐치하기 때문에 three two one 순으로 출력이 된다.

이벤트 캡처링

이벤트 버블링과는 반대로 특정 요소에 이벤트가 발생하면 window 에서 시작해서 이벤트 타깃 요소를 만날 때까지 하위 요소로 내려가면서 해당 이벤트가 전달되는 특성을 말한다.

캡처링 예제

script 태그의 내용을 다음과 같이 바꿔준다.

캡처링은 addEventListener() 의 3번째 인수로 capture 프로퍼티의 값이 true인 객체를 넣어주면 된다.

const $divs = document.querySelectorAll('div');
$divs.forEach(div =>
  div.addEventListener('click', e => console.log(e.currentTarget.className), {
    capture: true,
  })
);

위의 예제에서 Three를 클릭하면 다음과 같이 one two three 순으로 출력이 된다.

이것도 버블링과 마찬가지로 이벤트는 동일하게 전파되지만 현재 이벤트 핸들러는 캡처링 단계의 이벤트를 캐치하기 때문에 one two three 순으로 출력이 된다.

이벤트 전파 방지

이벤트 객체의 stopPropagation() 메서드는 이벤트 전파를 중지 시킨다.

const $divs = document.querySelectorAll('div');
$divs.forEach(div =>
  div.addEventListener('click', e => {
    e.stopPropagation();  // 이벤트 전파 중단
    console.log(e.currentTarget.className);
  })
);

다음과 같이 e.stopPropagation() 을 호출하면 위의 예제에서 이벤트 버블링의 경우 three만 출력이 될 것이고 캡처링의 경우 one 만 출력이 될 것이다.

버블링을 막아야할까?

꼭 필요한 경우를 제외하곤 버블링을 막지 않는 것을 권장한다.

예를 들어 사람들이 페이지에서 어디를 클릭했는지 클릭 이벤트를 감지하여 분석하는 시스템을 도입한 경우 stopPropagation 으로 버블링을 막아놓은 영역은 분석 시스템의 코드가 동작하지 않아서 분석이 제대로 되지 않는다고 한다.

이벤트 버블링을 막아야 하는 경우 거의 없으며 버블링을 막아야 해결되는 문제라면 커스텀 이벤트 등을 사용해 문제를 해결할 수 있다.

이벤트 위임

이벤트 위임은 캡처링과 버블링을 활용한 이벤트 핸들링 패턴으로 비슷한 방식으로 여러 요소를 다뤄야 할 때 사용된다.

이벤트 위임을 사용하면 요소마다 핸들러를 할당하지 않고, 요소의 공통 조상에 이벤트 핸들러를 단 하나만 할당해도 여러 요소를 한꺼번에 다룰 수 있다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
  </head>
  <body>
    <h1>오늘의 할 일</h1>
    <ul class="itemList">
      <li>
        <input type="checkbox" id="item1" />
        <label for="item1">JS 공부하기</label>
      </li>
      <li>
        <input type="checkbox" id="item2" />
        <label for="item2">알고리즘 공부하기</label>
      </li>
    </ul>
    <script>
      const $inputs = document.querySelectorAll('input');
      $inputs.forEach(input => input.addEventListener('click', e => alert('clicked')));
    </script>
  </body>
</html>

위의 코드는 li 요소를 추가할 때마다 추가로 input 에 핸들러를 달아야 한다.

위의 문제를 이벤트 위임으로 해결해보자.

const $itemList = document.querySelector('.itemList');

$itemList.addEventListener('click', e => {
  if (e.target.closest('input')) {
		alert('clicked');
	}
});

위의 코드처럼 바꿔도 이벤트 버블링에 의해 정상 동작하며 이벤트 핸들러를 상위 요소 한 개에만 달 수 있게 됐다.

이벤트 위임을 통해 하위 DOM 요소에서 발생한 이벤트를 처리할 때 주의할 점은 상위 요소에 이벤트 핸들러를 등록하기 때문에 이벤트 타깃, 즉 이벤트를 실제로 발생시킨 DOM 요소가 개발자가 기대한 DOM 요소가 아닐 수도 있다는 것이다.

따라서 이벤트 반응이 필요한 DOM 요소에 한정하여 이벤트 핸들러가 실행되도록 이벤트 타깃을 검사할 필요가 있다.

위의 코드에선 if (e.target.closest('input')) 으로 검사를 해주었다.

Element.closest(selector)

Element의 요소부터 시작해 위로 올라가면서 selector와 일치하는 가장 근접한 조상 요소를 반환하며 조건에 만족한 요소가 없으면 null을 반환한다.

이벤트 위임의 장점

  • 많은 핸들러를 할당하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약된다.
  • 요소를 추가하거나 제거할 때 해당 요소에 할당된 핸들러를 추가하거나 제거할 필요가 없기 때문에 코드가 짧아진다.

이벤트 위임의 단점

  • 이벤트 위임을 사용하려면 반드시 이벤트 버블링이 되어야 하지만, 몇몇 이벤트는 버블링이 되지 않는다.
    • 포커스 이벤트: focus / blur
    • 리소스 이벤트: load / unload / abort / error
    • 마우스 이벤트: mouseenter / mouseleave

Reference

책 - 모던 자바스크립트 Deep Dive

버블링과 캡처링

이벤트 위임

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

profile
알고리즘과 프론트엔드 부셔버리기

0개의 댓글