ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Javascript - Event Delegation
    Javascript 2023. 6. 23. 22:53

     

    🔔 서론

    프로그래머스 데브코스 Javascript 심화과정을 수강하던 중 생소한 키워드를 알게 됐다. 그것은 Event Delegation 즉, 이벤트 위임 이라는 개념이었다.

    강사님께서 예시 코드를 보여주시면서 이벤트 델리게이션의 개념을 알아두는 게 정말 중요하다고 스치듯 말씀하셨지만, 내 귀에는 엄청 강조하듯이 들린 것 같은 느낌이었고 마침 처음 듣는 개념이었기에 정리를 하고자 했다.

    Event Delegation에 대해 알기 위해서는 Event Flow, Event Capturing, Event Bubbling등 여러가지 개념을 또 알아야 했기에 이번 포스팅에서 모두 알아보도록 하자. 

     

    📌 Event Delegation이란?

    이벤트 델리게이션은 Javascript의 대표적인 디자인 패턴 중 하나이다. 브라우저에서 사용자의 '클릭', '호버' 등과 같은 동적인 이벤트를 관리하기 위해서 DOM에서 이벤트를 작성하는 '이벤트 리스너(Event Listener)'가 있는데, 이벤트 델리게이션은 이 이벤트 리스너를 보다 더 효율적으로 관리하기 위한 디자인 패턴이다.

    자바스크립트의 대부분 이벤트는 이벤트 흐름(Event Flow)의 이벤트 캡처링(Event Caputring), 이벤트 버블링(Event Bubbling) 단계를 거친다. 일반적으로 addEventListener 메서드의 세 번째 매개변수로 true를 전달하지 않는 이상 이벤트 캡처링 단계는 수행되지 않고 이벤트 버블링 단계를 거치는데, 이들의 용어와 구조를 살펴보며 Event Delegation을 알아보자.

     

    📌 Event Flow

    다음 평범한 html 코드가 있다

    <html>
      <body>
        <div>
          <p>Click Me!</p>
        </div>
      </body>
    </html>

    위의 코드에서 body태그, div태그, p태그 각각 클릭 했을 때 함수가 동작되도록 이벤트 핸들러를 연결 시켜보자.

    const $body = document.body;
    const $div = document.querySelector('div');
    const $p = document.querySelector('p');
    
    $body.addEventListener('click', () => {
      console.log('Body Event Handler!');
    })
    
    $div.addEventListener('click', () => {
      console.log('Div Event Handler!');
    })
    
    $p.addEventListener('click', () => {
      console.log('P Event Handler!');
    })

    이 때 p태그의 'Click Me'를 클릭하게되면 어떤 이벤트 핸들러가 동작하게 될까?

    정답은 '순서대로 셋 다 실행된다' 이다.

    🤔 순서? 무슨 순서?

    이 이벤트 들이 실행되는 순서를 Event Flow 라고 부른다.

    우선 어디에서든지 이벤트가 발생하면 root에서 가장 가까운 순으로 이벤트가 실행되는데, 위의 예제 코드에서는 body태그가 이벤트 핸들러가 연결된 가장 가까운 태그이니 body태그에 연결된 이벤트 핸들러가 먼저 실행이된다. 그 다음 div태그에 연결된 이벤트 핸들러가 실행되는데 이 다음 p태그에 연결되기 전까지의 단계를 Capture Phase라고 한다.

    그 후 p태그에 연결된 이벤트 핸들러가 동작하고 이 단계를 Target Phase라고 한다.

    target이었던 p에 연결된 이벤트 핸들러가 실행되고 나면 다시 역순으로 root까지 돌아가며 이벤트 핸들러들을 실행시키는데 즉, div태그와 body태그에 연결된 이벤트 핸들러들이 div, body 순으로 다시 한번 실행된다는 것이다. 이 단계를 Bubble Phase라고 하며 이 Bubble Phase를 끝으로 이벤트는 종료된다.

    보기쉽게 그림으로 Event Flow를 표현하면 다음과 같다.

    그런데 이대로라면은 target을 제외한 상위 요소에 연결된 이벤트 핸들러들이 두번씩 실행이 되는데, 브라우저에서 이를 막기 위해 target과 currentTarget이 일치 하지 않는 이벤트 핸들러들에 대해서 한 번씩만 실행되게 제약을 걸고 Capture Phase에서 실행될 것인지 Bubble Phase에서 실행될 것인지 선택하게 한다.

    단, 여기서 target은 사용자가 클릭한 요소 즉, 위의 예제에서는 p태그이고, currentTarget은 Event Flow에 따라 현재 이벤트 핸들러가 실행중인 요소를 가리킨다.

    이벤트 핸들러가 Capture Phase에서 실행될 것인지 Bubble Phase에서 실행될 것인지 선택하는 방법은 addEventListener 메서드를 사용하면 된다. addEventListener의 구문은 다음과 같은데,

    target.addEventListener(type, listener[, useCapture])

    세 번째 인자인 useCapture 자리는 boolean값을 받는데, 여기에 true를 넣으면 Capture Phase에서 이벤트 핸들러가 실행되고, default 값인 false를 넣으면 Bubble Phase에서 실행되게 된다.

    따라서, 위의 body태그, div태그, p태그 각각 클릭 했을 때 함수가 동작되도록 이벤트 핸들러를 연결시킨 코드는 useCapture 인자를 비워뒀으니 default 값인 false가 들어가고, Bubble Phase에서 이벤트 핸들러가 실행되게 된다.

    Click Me!를 클릭한 결과(Bubble Phase)

    이번엔 useCapture 인자를 true로 두고 이벤트를 발생시켜보자.

    const $body = document.body;
    const $div = document.querySelector('div');
    const $p = document.querySelector('p');
    
    $body.addEventListener('click', () => {
      console.log('Body Event Handler!');
    }, true)
    
    $div.addEventListener('click', () => {
      console.log('Div Event Handler!');
    }, true)
    
    $p.addEventListener('click', () => {
      console.log('P Event Handler!');
    }, true)

    Click Me!를 클릭한 결과(Capture Phase)


    📌 Event Delegation

    하지만, 위의 방식들과 같은 코드의 문제점은 상위 요소의 이벤트도 실행시켜서 개발자가 원하지 않는 결과를 발생시킬 수 있다는 것이다. 따라서 여러 이벤트 리스너를 등록하지 않고, 상위 요소 하나에만 이벤트 리스너를 등록하고 어떤 요소에서 이벤트가 발생했는지 분기점을 나누는 패턴인 Event Delegation을 알아보자

    위에서 보았던 예제를 조금 변형시켜 이벤트 핸들러는 document하나에만 연결시키고, 클릭한 요소가 각각 body, div, p 일 때만 이벤트가 동작하게 조건문을 설정해준다.

    document.addEventListener('click', (e) => {
      if (e.target.tagName === "BODY"){
        console.log('Boby Event Handler!');
      } else if (e.target.tagName === 'DIV') {
        console.log('Div Event Handler!');
      } else if (e.target.tagName === 'P') {
        console.log('P Event Handler!');
      }
    })

    이렇게 코드를 수정 후 Click Me를 다시 클릭 해보자.

    클릭한 요소가 p태그였기 때문에 'P Event Handler!'만 실행된 모습을 볼 수 있다.

    이렇게 Event Delegation 패턴을 사용하면 가질 수 있는 이점이 크게 두 가지가 있는데,

    첫 번째는 메모리 사용량 감소이다. Javascript에서 함수는 객체다. 함수가 많으면 많을수록 메모리 사용량이 증가하게 되는데, 이벤트 핸들러 함수를 하나만 등록하게 되어 메모리 사용량이 줄어들게된다. 또한, 최상위 요소에만 접근을 하면 되므로 위의 코드에서 $body나 $div, $p와 같은 변수는 필요없기에 메모리 사용량이 역시 줄어들게 되고 이는 자바스크립트에서 퍼포먼스를 높이는 것에 영향을 줄 수 있다.

    두 번째는 개발자가 의도한 대로 동작한다는 것이다. 여러 개의 이벤트 리스너를 등록하게 되면, 이벤트 버블링에 의해 상위 요소의 이벤트 리스너도 실행이 된다. 이런 경우 의도치 않은 결과를 초래 하는 것을 막기 위해 Event Delegation 패턴을 사용하는 것이 안전하다.


    💊 정리

    1. Event Delegation 패턴은 여러 요소에 이벤트 리스너를 등록하지 않고 상위 요소에만 등록하여 원하는 요소에만 접근하는 방식이다
    2. Event Delegation을 적용하면 메모리 사용량을 감소시킬 수 있고, 개발자가 의도치 않은 동작을 방지 할 수 있다.
Designed by Tistory.