관리 메뉴

Jerry

이벤트 버블링(Event Bubbling), 이벤트 캡처링(Event Capturing), 이벤트 위임(Event Delegation), 이벤트 등록, 이벤트 흐름 정복하기!! 본문

Front/JavaScript

이벤트 버블링(Event Bubbling), 이벤트 캡처링(Event Capturing), 이벤트 위임(Event Delegation), 이벤트 등록, 이벤트 흐름 정복하기!!

juicyjerry 2021. 4. 28. 17:24
반응형

이번 시간은 이벤트 등록, 이벤트 버블링, 이벤트 캡처링, 이벤트 흐름, 이벤트 위임에 대해서 알아보겠습니다.

 

 

1. 이벤트 등록

이벤트 버블링, 캡쳐, 위임에 알아보기 위해 우리는 먼저 이벤트 등록에 대해서 알고 있어야 합니다.

 

이벤트 등록이란 웹 애플리케이션에서 사용자의 입력을 받기 위한 기능입니다. 

아래 코드 예시에서 addEventListener를 이용하여 이벤트를 등록할 수 있습니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Event 알아보기</title>
  </head>
  <body>
    <button>click!</button>
    <script>
      let button = document.querySelector("button");
      button.addEventListener("click", printConsole);

      function printConsole(event) {
        console.log(event);
      }
    </script>
  </body>
</html>

 

 

 

클릭 시, 해당 이벤트 내용이 콘솔에 찍히는 걸 확인할 수 있습니다.

 

 

 

 

추가로, 만약에 핸들러를 제거 해주고 싶다면, 어떻게 해야 할까요?

그럴 때는 '이벤트 등록'했던 것과 비슷하게 '이벤트 해제' 혹은 '이벤트 제거'를 해줘야 합니다.

removeEventListener를 같은 단계에 두고 사용하시면 핸들러가 지워집니다. 

 

 

2. 이벤트 버블링

이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되는 특성입니다. 

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

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Event 알아보기</title>
  </head>
  <body>
    <div class="1">
      1
      <div class="2">
        2
        <div class="3">3</div>
      </div>
    </div>
    <script>
      let divTag = document.querySelectorAll("div");
      console.log(divTag);
      divTag.forEach((div) => div.addEventListener("click", printConsole));

      function printConsole(event) {
        console.log(event.currentTarget.className);
      }
    </script>
  </body>
</html>

 

 

 

아래 이미지는 위 코드를 테스트하는 과정을 보여주고 있습니다.

 

3을 클릭하게 되면, 3 -> 2 -> 1 순으로 콘솔에 해당 숫자가 찍히는 걸 확인할 수 있습니다.

만약, 2나 1을 클릭하게 되면, 해당 숫자와 상위 태그의 숫자가 찍히게 됩니다.

(ex. 2 클릭 시, 2 -> 1 순으로, 1 클릭시, 1이 찍힘)

  

 

 

 

이런 흐름을 '이벤트 버블링'이라고 부릅니다. 이벤트가 제일 깊은 곳에 있는 요소(하위 요소)에서 시작해 부모 요소(상위 요소)를 거슬러 올라가며 발생하는 모양을 '거품(bubble)'과 닮아서 이런 명칭이 지어지게 되었다고 합니다.

 

출처: ko.javascript.info

 

 

 

3. 이벤트 캡처링 

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

실제 코드에서 자주 쓰이진 않지만, 종종 유용한 경우가 있다고 합니다. 

 

이벤트 캡처를 사용하려면 어떻게 해야 할까요?

이벤트 버블링 예시 코드에서 바뀐 부분을 찾아보세요!

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Event 알아보기</title>
  </head>
  <body>
    <div class="1">
      1
      <div class="2">
        2
        <div class="3">3</div>
      </div>
    </div>
    <script>
      let divTag = document.querySelectorAll("div");
      console.log(divTag);
      divTag.forEach((div) =>
        div.addEventListener("click", printConsole, { capture: true })
      );

      function printConsole(event) {
        console.log(event.currentTarget.className);
      }
    </script>
  </body>
</html>

 

 

 

찾으셨나요?

바로, 'addEventListener의 3번째 인자의 유무'였습니다!

 

참고로, #1과 #2 방식 상관없이 같은 동작을 합니다!

#1 div.addEventListener("click", printConsole, { capture: true })

 

#2 div.addEventListener("click", printConsole, true)

 

3번을 클릭 시, 1 -> 2 -> 3 순으로 콘솔에 해당 숫자 값이 찍히는 것을 확인할 수 있습니다.

2번을 클릭하면 1 -> 2 순으로, 1번은 1만 이렇게 콘솔에 찍힙니다. 

 

 

 

 

4. 이벤트 흐름

이미 이벤트 버블링과 이벤트 캡처 부분을 보시고 오셨다면 혹은 알고 계시다면

이번 챕터는 전체적인 흐름을 익히는 시간이라고 생각해주시고 보시면 될 것 같습니다.

 

표준 DOM 이벤트에서 정의한 3가지 단계의 이벤트 흐름이 있습니다.

 

1. 캡쳐링 단계 - 이벤트가 하위 요소로 전파되는 단계

2. 타깃 단계 - 이벤트가 실제 타깃 요소에 전달되는 단계

3. 버블링 단계 - 이벤트가 상위 요소에 전파되는 단계

 

 

출처: ko.javascript.info

 

 

 

1. 이벤트 캡처링 단계 |  <td> 클릭 시, 이벤트가 최상위 조상에서 시작해 아래로 전파가 됩니다. 

2. 타깃 단계 |  이벤트가 타깃 요소에 도착해 실행된 됩니다.

3. 이벤트 버블링 단계 | 다시 상위 요소로 전파됩니다. 

 

이벤트 캡처링을 하려면 3번째 인자에 true 값을 넣어주어야 한다고 했던 것 기억하시나요?

capture 옵션이 false가 default값으로, 이때 핸들러는 버블링 단계에서 동작하고, 

capture 옵션이 true가 될 때, 이때 핸들러는 캡처링 단계에서 동작합니다. 

 

참고로, 타깃 단계는 별도로 처리되지 않는다고 하며, 단지 캡처링과 버블링 단계의 핸들러가 타깃 단계에서 트리거(발동 / 작동)된다고 생각하시면 됩니다.  

 

 

5. 이벤트 버블링, 이벤트 캡처링 중단하기

원하는 화면 요소의 이벤트만 동작하도록 하고 싶을 경우에 버블링을 중단할 수 있는 명령이 있습니다. 

이벤트 객체의 메서드인 event.stopPropagation()를 사용하면 됩니다. 

이 메서드(API)는 해당 이벤트가 전파되는 걸 막습니다. 다시 말해, 상위 요소로 전달을 막는다는 겁니다.

 

이벤트 버블링의 경우에는 클릭한 요소의 이벤트만 발생하며,

이벤트 캡처링의 경우에는 클릭한 요소의 최상위 요소의 이벤트만 동작시켜 하위 요소에 전달하지 않습니다.

 

참고로, event.stopImmediatePropagation() 라는 메서드도 있다고 합니다.

이벤트 핸들러가 여러개인 상황에서 event.stopPropagation()는 하나의 동작은 막더라도 나머지 핸들러의 동작은 막지 못하는데, 

 event.stopImmediatePropagation() 메서드는 해당 요소에 할당된 특정 이벤트 외의 나머지 핸들러의 동작까지 막아준다고 합니다.

 

 

6. 이벤트 위임

마지막입니다!!

여기까지 오셨다면, 이번 챕터도 이지(EASY)하게 이해하실 겁니다.

 

이벤트 위임은 이벤트 버블링이나 이벤트 캡처링을 활용할 때 사용되는 이벤트 핸들러 패턴입니다.

이벤트 위임을 사용하기 위해선 조건이 있습니다.

 

별건 아니고, 이벤트 위임이 활용되는 환경은 비슷한 방식으로 여러 요소를 다뤄야 할 때입니다. 

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

 

즉, ‘하위 요소에 각각 이벤트를 붙이지 않고 상위 요소에서 하위 요소의 이벤트들을 제어하는 방식’이라고 요약할 수 있습니다. 

 

 

아래 코드는 체크 박스를 클릭 시, 콘솔에 'clicked'라고 찍히는 이벤트 동작을 보여줍니다.

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Event 알아보기</title>
  </head>
  <body>
    <ul class="inputList">
      <li>
        <input type="checkbox" id="item1" />
        <label for="item1">이벤트 동작을 확인하자</label>
      </li>
      <li>
        <input type="checkbox" id="item2" />
        <label for="item2">이벤트 동작을 확인하자</label>
      </li>
    </ul>

    <script>
      let input = document.querySelectorAll("input");
      input.forEach((input) =>
        input.addEventListener("click", (event) => console.log("clicked"))
      );
    </script>
  </body>
</html>

 

 

 

 

 

이후에, 체크박스를 추가해준다면 어떻게 해야 할까요?

 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Event 알아보기</title>
  </head>
  <body>
    <ul class="inputList">
      <li>
        <input type="checkbox" id="item1" />
        <label for="item1">이벤트 동작을 확인하자</label>
      </li>
      <li>
        <input type="checkbox" id="item2" />
        <label for="item2">이벤트 동작을 확인하자</label>
      </li>
    </ul>

    <script>
      let input = document.querySelectorAll("input");
      input.forEach((input) =>
        input.addEventListener("click", (event) => console.log("clicked"))
      );

      let inputList = document.querySelector(".inputList");
      let li = document.createElement("li");
      let inputTag = document.createElement("input");
      let label = document.createElement("label");
      let labelText = document.createTextNode("이벤트 위임 학습");

      inputTag.setAttribute("type", "checkbox");
      inputTag.setAttribute("id", "item3");
      label.setAttribute("for", "item3");
      label.appendChild(labelText);
      li.appendChild(inputTag);
      li.appendChild(label);
      inputList.appendChild(li);
    </script>
  </body>
</html>

 

 

 

체크 박스를 추가 후, 클릭을 했지만 콘솔에 아무것도 찍히지 않는 것을 볼 수 있습니다.

 

 

 

 

위 모습처럼

이벤트 리스너가 동작이 되지 않는 이유는 '이벤트를 등록하는 시점' 이후에 추가를 했기 때문입니다.

그래서 추가된 이벤트는 등록이 되지 않아 원하는 이벤트 동작이 발생하지 않은 것입니다. 

 

이런 예시처럼 추가되는 상황이 발생하면서 이벤트 리스너에 추가해야 하는 작업은 불편해질 수밖에 없습니다. 

이때 불편함을 해결할 수 있는 방법이 이벤트 위임입니다.

 

기존의 이벤트 등록 코드 #1을 #2로 바꿔주었습니다.

 

  #1
	let input = document.querySelectorAll("input");
      input.forEach((input) =>
        input.addEventListener("click", (event) => console.log("clicked"))
      );

	
  #2
      let input = document.querySelector(".inputList");
      input.addEventListener("click", (event) => console.log("clicked"));

 

 

 

그렇게 되면, 나중에 추가한 이벤트도 정상적으로 동작하는 것을 확인할 수 있습니다!

상위 요소인 .inputList에 이벤트 리스너를 달아놓고 하위에서 발생한 클릭 이벤트를 감지합니다. (이벤트 버블링)

 

 

 

 

여기서 한 가지 문제가 있는데, label를 클릭하여도 이벤트가 동작한다는 것이다.

이럴 때는 elem.closest(selector) 메서드를 활용할 수 있다.

 

 elem.closest(selector) 메서드는 elem의 상위 요소 중 selector와 일치하는 가장 근접한 조상 요소를 반환합니다. 

 

 

이상으로 이벤트 등록부터 시작해 이벤트 버블링, 이벤트 캡처링, 이벤트 흐름, 이벤트 위임까지 살펴보았습니다.

여기까지 읽어주셔서 감사합니다 :)) 

 

 

 

 

출처: 캡틴판교 | bit.ly/2R5NiUu

출처: javascript.info | bit.ly/3t1YizA

출처: javascript.info | bit.ly/3aJ0CFo

반응형