치춘짱베리굿나이스

이벤트 버블링과 캡쳐링 본문

Javascript + Typescript/이론과 문법

이벤트 버블링과 캡쳐링

치춘 2023. 7. 13. 20:41

이벤트 버블링, 캡쳐링

다른 과제 얘기하다가 나왔는데 사실 내가 이벤트 버블링과 캡쳐링에 관해 확실히 짚고 넘어가질 않아가지고 약간 얼떨떨했던 기억이…

이번 기회에 좀 되짚고 가려고 한다

세 가지 이벤트 흐름

이벤트 흐름이란? 이벤트가 발생하였을 때 DOM 트리에서 이를 수신하는 세 가지 단계를 뜻한다

1. 캡쳐링 단계

window (최상위 요소) 부터 이벤트가 발생한 위치 (target) 까지 이벤트가 전파되는 단계이다

이벤트는 최상위 요소에서 시작해 아래로 전파된다

2. 타깃 단계

이벤트가 실제 target 으로 전달되는 단계이다

캡쳐링 단계에서 최상위부터 전파되던 이벤트는 타깃에 도착한 뒤 실행된다

3. 버블링 단계

이벤트가 target 으로부터 상위 요소로 전파되는 단계이다

타깃 단계에서 실행된 이벤트가 다시 상위 요소를 향해 위로 전파된다

이벤트 버블링

간단하게 그려봤다

특정 요소 (이미지에서는 button) 에서 이벤트가 발생하면, 해당 이벤트가 상위의 요소까지 흘러들어가는 현상을 버블링이라고 한다

예시

<!DOCTYPE html>
<html lang="en">
  <head>
        ...
  </head>
  <body>
    <div>
      <button>버튼</button>
    </div>
  </body>
    <script>
    const div = document.querySelector("div");
    const btn = document.querySelector("button");

    function handleClick(where) {
      return () => console.log(where);
    }

    window.addEventListener("click", handleClick("window"));
    document.addEventListener("click", handleClick("document"));
    document.body.addEventListener("click", handleClick("body"));
    div.addEventListener("click", handleClick("div"));
    button.addEventListener("click", handleClick("button"));
  </script>
</html>

위의 코드를 실행시켰을 때, 트리에서 최하위에 있는 button 을 클릭하여 이벤트 핸들러를 동작시키면, div, body, document, window 에 있는 핸들러가 순차적으로 모두 동작한다

이처럼 한 요소에서 이벤트가 발생하면, 이 요소에 걸린 핸들러가 동작하고, 버블링에 의해 부모, 조부모, 증조부모… 의 핸들러가 순차적으로 동작한다

이 버블링은 가장 최상단의 요소 (대개 document 또는 window) 를 만날 때까지 계속 발생한다

동작 순서

  1. 버튼을 클릭한다
  2. button에 붙은 onclick 핸들러가 동작한다
  3. 이어서 button의 부모 요소인 div에 붙은 onclick 핸들러가 동작한다
  4. 이어서 div의 부모 요소인 body에 붙은 onclick 핸들러가 동작한다
  5. 이어서 body의 부모 요소인 document에 붙은 onclick 핸들러가 동작한다
  6. 이어서 document의 부모 요소인 window에 붙은 onclick 핸들러가 동작한다
  7. window 가 최상위 요소이기 때문에 이벤트 전파가 중지된다

따라서 콘솔창에서도 buttondivbodydocumentwindow 의 순으로 출력된다

마치 거품이 위로 타고 올라가듯이 이벤트가 전파된다고 해서 버블링이라고 한다

용례

<!DOCTYPE html>
<html lang="en">
  <head>
        ...
  </head>
  <body>
    <div id="div">
      <button id="button1">버튼1</button>
      <button id="button2">버튼2</button>
      <button id="button3">버튼3</button>
      <button id="button4">버튼4</button>
      <button id="button5">버튼5</button>
    </div>
  </body>
  <script>
    const div = document.getElementById("div");

    function handleClick() {
      return (e) => {
        console.log(e.target.id);
      };
    }

    div.addEventListener("click", handleClick());
  </script>
</html>

위와 같이 자식 요소가 여러 개일 때 (또는 몇 개인지 모를 때) 핸들러를 일일이 달기보다는, 부모 요소 하나에만 핸들러를 부착해 버블링을 사용하여 자식 요소의 이벤트를 포착하면 편리하다

이러한 구현 방법을 이벤트 위임이라고 하는데, 이를 활용하면 버튼의 개수나 구조와 상관없이 이벤트를 부착하고 핸들링할 수 있어 확장성에 좋으며, 구조가 유연해진다

또한 핸들러를 여러 개 선언할 필요 없이 단 하나의 핸들러로 다수의 이벤트 처리가 가능하므로 메모리 절약에 좋다

이벤트 버블링 막기

<!DOCTYPE html>
<html lang="en">
  <head>
        ...
  </head>
  <body>
    <div id="div">
      <button id="button">버튼</button>
    </div>
  </body>
  <script>
    const div = document.getElementById("div");
    const btn = document.getElementById("button");

    function handleClick(where) {
      return (e) => {
        e.stopPropagation();
        console.log(where);
      };
    }

    window.addEventListener("click", handleClick("window"));
    document.addEventListener("click", handleClick("document"));
    document.body.addEventListener("click", handleClick("body"));
    div.addEventListener("click", handleClick("div"));
    button.addEventListener("click", handleClick("button"));
  </script>
</html>

이벤트 핸들러의 인자인 e (e: Event) 의 stopPropagation() 함수를 호출하면 이벤트의 전파를 막을 수 있다

아까와 달리 div ~ window 까지의 핸들러가 실행되지 않는다

다만 특별한 경우를 제외하곤 버블링을 막지 않는 것이 좋다고 한다

버블링되지 않는 이벤트들

  • load, unload, scroll
  • focus, blur
  • DOMNodeRemovedFromDocument, DOMNodeInsertedIntoDocument
  • loadstart, progress, error, abort, load, loadend

이벤트 캡쳐링

이벤트 캡쳐링은 버블링과 반대로, 특정 이벤트가 발생했을 시 상위 요소부터 하위 요소로 전파되어 나가는 것을 말한다

이벤트 핸들러를 부착할 때 세 번째 인자로 { capture: true } 를 지정하면 캡쳐링 단계를 이용할 수 있다

버블링에 비해 자주 쓰이진 않는다고 한다

예시

<!DOCTYPE html>
<html lang="en">
  <head>
        ...
  </head>
  <body>
    <div id="div">
      <button id="button">버튼</button>
    </div>
  </body>
  <script>
    const div = document.getElementById("div");
    const btn = document.getElementById("button");

    function handleClick(where) {
      return (e) => {
        console.log(where);
      };
    }

    window.addEventListener("click", handleClick("window"), {
      capture: true,
    });
    document.addEventListener("click", handleClick("document"), {
      capture: true,
    });
    document.body.addEventListener("click", handleClick("body"), {
      capture: true,
    });
    div.addEventListener("click", handleClick("div"), {
      capture: true,
    });
    button.addEventListener("click", handleClick("button"), {
      capture: true,
    });
  </script>
</html>

이벤트 캡쳐링은 이벤트 핸들러 부착 (addEventListener) 시 세 번째 인자로 true (또는 capture: true) 를 넘겨줌으로서 감지해낼 수 있다

캡쳐링을 이용하여 이벤트를 포착하면 버블링의 역순으로 출력된다 → 캡쳐링은 상위 요소에서 하위 요소로 내려가는 방식이기 때문

이벤트 캡쳐링과 버블링 순서 확인하기

<!DOCTYPE html>
<html lang="en">
  <head>
        ...
  </head>
  <body>
    <div id="div">
      <button id="button">버튼</button>
    </div>
  </body>
  <script>
    const div = document.getElementById("div");
    const btn = document.getElementById("button");

    function handleClickCaptured(where) {
      return (e) => {
        console.log("captured: " + where);
      };
    }
    function handleClickBubbled(where) {
      return (e) => {
        console.log("bubbled: " + where);
      };
    }
        window.addEventListener("click", handleClickCaptured("window"), {
      capture: true,
    });
    window.addEventListener("click", handleClickBubbled("window"));
    document.addEventListener("click", handleClickCaptured("document"), {
      capture: true,
    });
    document.addEventListener("click", handleClickBubbled("document"));
    document.body.addEventListener("click", handleClickCaptured("body"), {
      capture: true,
    });
    document.body.addEventListener("click", handleClickBubbled("body"));
    div.addEventListener("click", handleClickCaptured("div"), {
      capture: true,
    });
    div.addEventListener("click", handleClickBubbled("div"));
    button.addEventListener("click", handleClickCaptured("button"), {
      capture: true,
    });
    button.addEventListener("click", handleClickBubbled("button"));
  </script>
</html>

위와 같은 순서로 타겟이 캡쳐링 → 버블링되는 것을 볼 수 있다

타깃 단계는 핸들러가 부착되어 실행되는 시점이 된다


참고 자료

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

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

https://ko.javascript.info/event-delegation

'Javascript + Typescript > 이론과 문법' 카테고리의 다른 글

UnhandledPromiseRejection  (0) 2023.07.17
[Typescript] 인덱스 시그니쳐  (0) 2023.07.14
스코프, 렉시컬 스코프  (0) 2023.06.26
[Violation] took 1000⬆️ms  (0) 2022.09.21
이벤트 핸들러 this 오류  (0) 2022.09.21
Comments