이벤트 핸들링👊 - 이벤트 버블링(Event Bubbling) | 캡처(Event Capture)| 위임(Event Delegation)

zaman·2022년 9월 8일
0

Javascript | Basics

목록 보기
2/8
post-thumbnail

1. 이벤트 버블링과 캡처링


계층적 구조를 가진 HTML 요소에 이벤트가 발생할 경우 연쇄적 반응이 일어나는데 이것을 이벤트 전파(Event Propagation)라고 한다.
그리고 이벤트 전파를 전파 방향에 따라 두 가지로 구분할 수 있는데 이것이 바로 이벤트 버블링(Event Bubbling)과 캡처링(Event Capturing)이다.

⚠️ 이벤트 버블링과 캡처링은 택 1이 아니고 캡처링부터 시작해 버블링으로 종료된다고 한다 → 캡처링, 버블링은 순차적으로 발생!


<td> 클릭
→ 최상위 요소에서 아래로 전파 : Capturing phases
→ 이벤트가 타겟 요소에 도착되어 실행: Target phase
→ 다시 위로 전파 : Bubbling phases

이러한 과정을 통해 요소에 할당된 이벤트 핸들러가 호출된다.

➕ addEventListener 메소드의 세번째 매개변수에 true를 설정하면 캡처링으로 전파되는 이벤트를, false 또는 미설정하면 버블링으로 전파되는 이벤트를 캐치

1. 이벤트 버블링(Event Bubbling)

자식요소에서 이벤트가 발생했을 때 해당 이벤트가 상위 화면 요소들에게 전파되는 특성

이벤트 버블링은 더 이상 부모요소가 없을 때까지 하위에서 상위 요소로 이벤트가 전파되는 것을 말한다

button ➡️ div ➡️ 상위 div ➡️ body

  <body>
    <div class="one">
      <div class="two">
        <button class="three">click</button>
      </div>
    </div>
  </body>
const one = document.querySelector(".one");
const two = document.querySelector(".two");
const three = document.querySelector(".three");

document.body.addEventListener("click", () => {
  console.log("0. body");
});
one.addEventListener("click", () => {
  console.log("1. div");
});
two.addEventListener("click", () => {
  console.log("2. div");
});
three.addEventListener("click", () => {
  console.log("3. button");
});

이렇게 이벤트를 등록시키고 버튼(three)을 클릭하면 콘솔에 0, 1, 2, 3이 모두 뜨는 것을 확인할 수 있다
즉, 가장 하위 요소에서 발생한 이벤트가 상위 요소들로 전파된 것!

cf) 버블링으로 전파되지 않는 이벤트

- focus 
- blur 
- mouseenter 
- mouseleave 

만약 이 이벤트들을 버블링하고 싶다면 아래와 같이 바꾸어 사용하면 된다.
다음 이벤트는 오직 버블링에서만 차이가 나고 동작은 같다

- focus ➡️ focusin
- blur ➡️ focusout
- mouseenter ➡️ mouseover
- mouseleave ➡️ mouseout

cf) 이벤트 버블링 막기

참고로 이벤트 버블링은 인위적으로 막을 수도 있다

event.stopPropagation() 실행

Event 인터페이스의 stopPropagation() 메서드는 현재 이벤트가 캡처링/버블링 단계에서 더 이상 전파되지 않도록 방지합니다. 전파를 방지해도 이벤트의 기본 동작은 실행되므로, stopPropagation()이 링크나 버튼의 클릭을 막는 것은 아닙니다. 이런 기본 동작을 방지하려면 preventDefault() 메서드를 사용하세요. 또한, stopPropagation()은 같은 이벤트 대상에 부착한 다른 수신기까지 막지는 않습니다. 이것까지 막으려면 stopImmediatePropagation()를 사용하세요.
출처 MDN

근데 막을 일이 없어서 거의 사용되진 않는다고 한다

2. 이벤트 캡쳐(Event Capture)

이벤트가 발생했을 때 최상위 요소 body에서 해당 자식 요소를 찾아 내려가는 방식으로 이벤트 버블링과 반대 방향으로 진행되는 전파 특성

간단한 예시를 통해 확인해보자

<body>
  <p>캡처링 이벤트 <button>버튼</button></p>
</body>

addEventListener 메소드의 세번째 매개변수에 true를 설정해 캡처링만 캐치해 확인해보자

    const body = document.querySelector('body');
    const para = document.querySelector('p');
    const button = document.querySelector('button');

    // 캡처링
    body.addEventListener("click", () => {
      console.log("0. body");
    }, true);

    // 캡처링
    para.addEventListener('click', () => {
      console.log('1. p.');
    }, true);

    // 캡처링
    button.addEventListener('click', () => {
      console.log('2. button');
    }, true);

이렇게 이벤트를 등록하고 버튼을 누르면 콘솔에 0, 1, 2가 모두 뜨는 것을 확인할 수 있다

캡처링은 상대적으로 자주 사용되지 않는다고 한다

이벤트 위임(Event Delegation)

자신에게 발생하는 이벤트를 다른 요소에서 처리하도록 하는 것
버블링과 캡처링을 이용해 실행 가능!

🤔 이벤트 위임 왜 사용하는걸까?

다음과 같이 li 요소가 있고 여기 이벤트 핸들러를 바인딩한다고 하면 4개의 이벤트 핸들러를 바인딩 해야한다.

<body>
    <div id="box">
      <ul id="list">
        <li id="li1" class="on">li 1</li>
        <li id="li2">li 2</li>
        <li id="li3">li 3</li>
        <li id="li4">li 4</li>
      </ul>
    </div>
</body>

만약 1000개의 li 요소가 있다면? 1000개의 이벤트 핸들러를 바인딩하게 될 것이다. 또한 동적으로 li 요소를 추가해야한다면 아직 추가되지 않은 li는 DOM에 존재하지 않아 이벤트 핸들러를 바인딩 할 수 없는 문제가 생길 것이다.

➡️ 이벤트를 위임해서 사용하면 메모리도 줄이고 효율성도 향상시킬 수 있겠군!

이벤트 위임을 통해 각각 요소에 이벤트 핸들러를 바인딩하는 대신 부모 요소에 이벤트 핸들러를 바인딩할 수 있다.

 const list = document.getElementById("list");
      const lis = list.children;

      function clickHandler(event) {
        for (i of lis) {
          i.classList.remove("on");
        }
       target.classList.add("on");
      }
// li에서 발생한 이벤트를 부모요소인 ul에 등록된 이벤트 핸들러를 실행시켜줌 → 이벤트 위임!
      list.addEventListener("click", clickHandler);

이렇게 코드를 수정하면 li의 개수가 늘거나 줄더라도 하나의 이벤트 핸들러로 관리할 수 있다 (list.addEventListener("click", clickHandler);)
또한 li의 개수에 따라 코드를 수정할 필요가 없기 때문에 유지보수도 쉽다.


출처
javascript deep dive
https://www.youtube.com/watch?v=beLituqpwl8&t=1s

profile
개발자로 성장하기 위한 아카이브 😎

0개의 댓글