본문 바로가기

FRONTEND

[JavaScript] 이벤트 위임

출처: https://unsplash.com/ko/%EC%82%AC%EC%A7%84/0u_x2Ma_muU

 

이벤트 위임에 대해서 이해하기에 앞서, JavaScript에서 이벤트가 발생하는 단계(phases of JavaScript event)에 대해 알아보자

 

Phases of JavaScript event

HTML Document에서 DOM(Document Object Model)을 통해 JavaScript를 통해 접근할 수 있는 인터페이스를 제공한다! DOM은 트리구조로 되어있기 때문에 상위 노드로 올라가면서 button을 포함하는 div, 이 div를 포함하는 body가 있고 이런 순서를 따라가다 보면 결국 html에 도달하게 된다. 만약 button을 클릭하여 어떠한 이벤트를 발생 시킨다면 해당 이벤트는 이 button을 감싸고 있는 상위 객체 또는 button내에 있는 하위 객체로 전달된다. 이러한 전달 과정을 자세히 살펴보면 다음과 같다.

 

브라우저 화면에서 이벤트가 발생하면 이벤트는 3가지 단계를 거쳐 전달된다.

 

  • 캡처링 단계(Capturing Phase) : 이벤트가 하위 요소로 전파되는 단계
  • 타겟 단계(Target Phase) : 이벤트가 실제 타겟 요소에 전달되는 단계(타겟이랑 이벤트가 발생한 지점)
  • 버블링 단계(Bubbling Phase): 이벤트가 상위 요소로 전달되는 단계(거품이 위로 올라오는 것으로 생각하면 된다)

 

구체적 예시

<DOCTYPE html>
  <html>
    <body id="bdy">
      <div id="container">
        <button id="btn">Click Me!</button>
      </div>
      <script type="text/javascript">
        document.getElementById('bdy')
          .addEventListener('click', function () {
            alert('Body!'); //3
          })
          document.getElementById('container')
          .addEventListener('click', function () {
            alert('Div!'); //1
          }, true)
          document.getElementById('btn')
          .addEventListener('click', function () {
            alert('Button!'); //2
          })
      </script>
    </body>
  </html>

 

위의 코드를 실행해보면, Div! → Button! → Body! 순서로 알림창이 뜨게된다.
왜 이런 순서로 이벤트가 발생할까?

먼저 addEventListener 함수의 3번째 인자에 대해 이해할 필요가 있다.

 


❗기본적으로 addEventListener 함수는 버블링 단계에서 호출된다! addEventListener 함수의 3번째 인자인 option 값을 boolean 타입으로 지정할 수 있는데 option을 true로 지정한다면 캡처링 단계에서 해당 이벤트를 실행시키고 false로 지정한다면 버블링 단계에서 해당 이벤트를 실행한다!

” If its last argument is true  the event handler is set for the capturing phase, if it is false the event handler is set for the bubbling phase. “

출처: https://javascript.info/bubbling-and-capturing

 

다음으로 이벤트 발생 단계 과정을 자세히 살펴보자

Click Me! 버튼을 클릭하게 되면, 최상위 요소(html 태그)에서 시작해 하위 방향으로 타겟(타겟이란 해당 이벤트가 발생한 요소를 말한다)을 찾는 캡처링 단계가 시작된다. 위에서 살펴본대로 3번째 인자가 true 인 함수를 상위에서 하위로 내려가면서 찾는 것이다.

가장 먼저 이벤트가 <body> 태그에 도달했을 때 Body! 알림창을 보여주는 함수는 작동하지 않는데, 그 이유는 해당 함수는 option의 값이 지정되지 않았으므로 버블링 단계에서 실행되기 때문이다.

아직 타겟을 발견하지 못했기 때문에 계속해서 하위로 내려가면서 <div> 태그로 넘어갔을 때 Div! 알림창을 보여주는 함수가 작동하는데 해당 이벤트 함수의 3번째 인자인 option의 값이 true로 설정되어있기때문에 캡처링 단계에서 해당 함수가 실행되는 것이다.

간단히 정리해보면,

1. Click Me! 버튼 클릭
2. 캡처링 단계가 시작되어 상위에서 하위로 내려가면서 캡처링 단계에 실행되어야 할 이벤트를 찾음
     HTML → BODY → DIV 단계로 이벤트 전달 (캡처링 단계)
3. 캡처링 단계에서 실행되어야 할 요소(타겟)인 DIV 를 찾아 해당 이벤트 발생시킴 (브라우저에 Div! 알림창 표시)
4. 캡처링 단계에서 실행시켜야할 모든 함수가 전부 실행되었으므로, 버블링 단계 시작
5. 하위에서 상위로 올라가면서 Button! Body! 순서로 실행됨 BUTTON → DIV → BODY → HTML (버블링 단계)

 

이벤트 위임

이제서야 이벤트 위임에 대해 이야기 할 수 있게 되었다.

 

이벤트 위임(Event Delegation)이란,
기본적으로 이벤트를 효율적으로 다루기 위한 일종의 패턴 이라 할 수 있다. 왜 이벤트를 효율적으로 다루어야 할 필요가 있을까?

 

예를 들어, 다음과 같은 코드를 작성했다고 하자.

<ul id="parent-list">
  <li id="post-1">Item 1</li>
  <li id="post-2">Item 2</li>
  <li id="post-3">Item 3</li>
  <li id="post-4">Item 4</li>
  <li id="post-5">Item 5</li>
  <li id="post-6">Item 6</li>
</ul>

 

<ul> 태그를 부모로 하는 다수의 <li>태그가 있고 상황에 따라 각각의 <li> 태그에 개별적 이벤트를 추가해야한다고 하자.

 

이 경우, 이벤트를 발생시키고 싶은 해당 <li> 태그에 직접 이벤트를 작성해주는 것으로 해결할 수 있다. 하지만 해당 <li> 태그의 수가 100개 혹은 그 이상이거나 특정 태그가 삭제되는 경우는 어떨까? 수 많은 태그가 변경되는 동안 일일히 직접 태그를 선택해 이벤트를 넣어준다는 것은 번거롭다. 이러한 경우 우리는 이벤트를 효율적으로 다루는 것의 필요성을 느끼게 된다.

이럴때 사용할 수 있는 것이 “이벤트 위임” 이다.
말그대로 이벤트를 발생시켜야할 요소의 상위 부모 요소로 이벤트를 위임 하는 것이다.

그 방법은,
공통 부모에 이벤트리스너를 추가하고 이벤트가 발생할 때 생성되는 이벤트 객체의 값을 이용하는 것이다.

해당 내용을 코드로 작성해보면 다음과 같다.

<ul id="parent-list">
  <li id="post-1">Item 1</li>
  <li id="post-2">Item 2</li>
  <li id="post-3">Item 3</li>
  <li id="post-4">Item 4</li>
  <li id="post-5">Item 5</li>
  <li id="post-6">Item 6</li>
</ul>


// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click", function(e) {
  // e.target is the clicked element!
  // If it was a list item
  if(e.target && e.target.nodeName == "LI") {
    // List item found!  Output the ID!
    console.log("List item ", e.target.id.replace("post-", ""), " was clicked!");
  }
});

우선 부모 요소에 이벤트리스너를 추가한다.

이벤트가 발생하면 이벤트 리스너가 어떤 종류의 이벤트가 발생했는지 감지하고 만약 그 요소가 <li> 요소 이면 해당 이벤트를 발생 시키면 되는 것이다.

 

Reference

 

Phases of JavaScript Event - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

 

Javascript - Event order

See section 7D of the book. Netscape 4 only supports event capturing, Explorer only supports event bubbling. Netscape 6 and Konqueror support both, while Opera and iCab support neither. On the Introduction to events page I asked a question that at first si

www.quirksmode.org

 

EventTarget.addEventListener() - Web API | MDN

EventTarget 인터페이스의 addEventListener() 메서드는 지정한 유형의 이벤트를 대상이 수신할 때마다 호출할 함수를 설정합니다.

developer.mozilla.org

 

이벤트 위임

 

ko.javascript.info

 

Event Delegation

Event delegation allows you to avoid adding event listeners to specific nodes; instead, the event listener is added to one parent.

davidwalsh.name