Event Bubbling | Capturing | Delegation

2023. 1. 4. 18:59프로그래밍/HTML & CSS

Event Bubbling (이벤트 버블링)

가장 깊게 중첩된 요소에 이벤트가 발생했을 때
이벤트가 위로 전달되는 것 (부모 요소들의 핸들러가 호출됨)
<body>
  <form onClick="alert('form')">FORM
    <div onClick="alert('div')">DIV
      <p onClick="alert('p')">P</p>
    </div>
  </form>
</body>

<p>에 할당된 핸들러 --> <div>에 할당된 핸들러 --> <form>에 할당된 핸들러

  • p 태그 클릭 : alert('p'), alert('div'), alert('form') 실행
  • div 태그 클릭 : alert('div'), alert('form') 실행
  • form 태그 클릭 : alert('form') 실행

 

<body>
  <form>FORM
    <div>DIV
      <p>P</p>
    </div>
  </form>
  <script>
    const form = document.querySelector('form');
    const div = document.querySelector('div');
    const p = document.querySelector('p');
    
    form.addEventListener('click', function () {
      alert(`target=${event.target.tagName}, this=${this.tagName}`);
    });
  </script>
</body>

부모 요소 핸들러에서는 어디에서 이벤트가 발생했는지 알 수 있음

  • p 태그 클릭 : target=p, this=form
  • div 태그 클릭 : target=div, this=form
  • form 태그 클릭 : target=form, this=form

 

event.target

실제 이벤트가 시작된 타깃 요소

 

event.currentTarget(this)

현재 요소, 현재 실행 중인 핸들러가 할당된 요소를 참조함

 

버블링 중단하기

<body>
  <form onClick="alert('form')">FORM
    <div onClick="alert('div')">DIV
       <p onClick="event.stopPropagation(); alert('p');">P</p>
    </div>
  </form>
</body>

이벤트 버블링은 타깃 이벤트에서 시작해서 document 객체를 만날 때까지 각 노드에서 모두 발생함

몇몇 이벤트는 window 객체까지 거슬러 올라가기도 함

event.stopPropagation() 메서드를 호출하면 버블링이 발생하지 않음

 

stopPropagation()

DOM 트리를 통한 이벤트 흐름을 중지함

하지만 브라우저 기본 동작을 취소하지는 않음

 

preventDefault()

이벤트의 기본 동작을 방지할 수 있음

하지만 이벤트 흐름을 중지하지는 않음


Event Capturing (이벤트 캡쳐링)

제일 상단에 있는 요소에서
아래로 이벤트가 내려오는 것

 

이벤트의 3단계 흐름

  1. 캡처링 단계 : 이벤트가 하위 요소로 전파되는 단계
  2. 타깃 단계 : 이벤트가 실제 타깃 요소에 전달되는 단계
  3. 버블링 단계 : 이벤트가 상위 요소로 전파되는 단계

 

이벤트 흐름 확인 방법

addEventListener 의 capture 옵션을 true 로 설정하기

 

p 태그 클릭 시

  • 캡쳐링 : HTML - BODY - FORM - DIV - P 로 진행됨
  • 버블링 : P - DIV - FORM - BODY - HTML 로 진행됨
  • 이벤트 흐름에서 타겟 단계는 별도로 처리되지 않음
<body>
  <form>
    FORM
    <div>
      DIV
      <p>P</p>
    </div>
  </form>
  <script>
    for (element of document.querySelectorAll('*')) {
      element.addEventListener('click', (event) => {
        alert(`캡처링: ${event.currentTarget.tagName}`);
      }, true);
      
      element.addEventListener('click', (event) => {
        alert(`버블링: ${event.currentTarget.tagName}`);
      });
    }
  </script>
</body>

Event Delegation (이벤트 위임)

하위 요소의 이벤트를
상위 요소에 위임(제어)하는 것
<body>
  <div id="buttons">
    <button class="buttonClass">Click</button>
    <button class="buttonClass">Click</button>
  </div>
  <script>
    const buttons = document.getElementsByClassName('buttonClass');
    for (const button of buttons) {
      button.addEventListener('click', () => {
        alert('clicked');
      });
    }
    
    let buttonList = document.querySelector('#buttons');
    let button = document.createElement('button');
    
    button.setAttribute('class', 'buttonClass');
    button.innerText = 'Click';
    buttonList.appendChild(button);
  </script>
</body>

div 태그 안의 두 button 요소를 클릭하면 alert('Click') 이 정상적으로 나옴

하지만 스크립트로 만든 button 요소를 클릭하면 alert('Click') 이 나오지 않음

 

이것은 버튼이 생기기 전에 이미 각 버튼 요소에 핸들러가 등록되었기 때문임

새로 생긴 버튼 요소에는 핸들러가 등록되어 있지 않음

 

해결 방법

현재는 하위 요소인 각 button 요소에서 이벤트를 제어하고 있음

새로운 하위 요소가 추가될 때마다 이벤트 리스너를 등록해주어야 함

 

하지만 하위 요소들이 존재하는 상위 요소에서 이벤트를 제어하면

하위 요소가 추가될 때마다 이벤트 리스너를 등록해주지 않아도 됨

 

const buttons = document.getElementById('buttons');
buttons.addEventListener('click', () => {
  if (e.target.className === 'buttonClass') {
    alert('clicked');
  }
});

상위 요소인 div에 이벤트 리스너를 추가해주면 해결 가능

하위 요소에서 발생한 클릭 이벤트를 이벤트 버블링을 통해서 감지하게 됨