본문 바로가기
CS/javascript

[javascript] 이벤트 위임 event delegation + bind(this)

by kyj0032 2024. 4. 19.

정의

1 2 3
6 5 4

 

이런 table의 td에 접근할 때, 각각의 td에 event handler를 할당하는 것이 아닌

table자체에 event handler 하나만 할당함으로써 여러 요소의 event handler를 한꺼번에 다룰 수 있음!

(캡처링, 버블링 베이스: https://kyj0032.tistory.com/125)

 

활용하기 1) menu의 button에 알맞은 함수 실행하기

menu안 button 각각에 event handler를 할당하는 대신, menu에서 button에 발생한 event를 받아서 맞는 함수를 처리할 수 있다.

<div id="menu">
  <button data-action="save">저장하기</button>
  <button data-action="load">불러오기</button>
  <button data-action="search">검색하기</button>
</div>

<script>
  class Menu {
    constructor(elem) {
      this._elem = elem;
      elem.onclick = this.onClick.bind(this); // (*)
    }

    save() {
      alert('저장하기');
    }

    load() {
      alert('불러오기');
    }

    search() {
      alert('검색하기');
    }

    onClick(event) {
      let action = event.target.dataset.action;
      if (action) {
        this[action]();
      }
    };
  }

  new Menu(menu);
</script>

 

* js class의 인스턴스 변수: https://kyj0032.tistory.com/126

this._elem = elem;

 

* data-* 속성: https://kyj0032.tistory.com/127

  <button data-action="save">저장하기</button>

 

* new Menu(menu)

new Menu(menu);

 

'menu'는 Menu class의 생성자로 id="menu"인 div 태그(HTML element)를 인자로 전달한다.

그렇게 Menu 클래스의 인스턴스가 생성된다.

 

onClick 메서드 binding

1. script의 제일 처음

new Menu(menu)에서 생성자 인자로 <div id="menu">라는 HTML element가 들어간다.

 

2. 생성자 실행

elem.onclick  = this.onClick.bind(this) 구문을 통해

elem, 즉 menu element의 onclick 함수는 항상 this.onClick == Menu 인스턴스의 함수를 가리키도록 한다.

 

좀 더 구체적으로 말하자면, bind(this)호출은 onClick 함수의 복사본을 생성한다. 이 복사본에 존재하는 this[action]()이 항상 생성자에서 전달된 this(지금의 Menu 인스턴스)를 가리키도록 한다.

 

3. button을 눌러 event를 발생시킨다. 이때 button 단계에선 onClick event handler가 없으니 넘어간다.

 

4. 이벤트 버블링, 이벤트 위임에 의해 <div id="menu">가 하위 요소 button의 event를 받는다.

만약 바인딩이 없다면 menu.onclick은 발생하지만 해당 onClick함수에서 this는 button(이벤트가 발생한 위치)을 가리키므로 함수가 정상적으로 실행되지 않는다.

 

완전 잘못 이해했다.

4-1. elem.onclick 구문이 아예 없으면

Menu 인스턴스의 onClick도 발생하지 않는다. 당연함

<div id="menu">와 Menu 인스턴스를 연결해줄 게 없다 .. 

 

4-2. elem.onclick = this.onClick으로 바인딩 없이

elem.onclick = this.onClick.bind(this);
//대신
elem.onclick = this.onClick;

onClick함수는 실행되지만 (당연함)

onClick함수 내부의 this를 읽지 못한다.(당연함)

 

bind가 없으면 Menu인스턴스가 아닌 <div id="menu>라는 DOM element를 가리킨다. 이렇게 되면 this의 save, load, search 메서드를 호출할 수 없다. 왜냐하면 Menu 인스턴스가 아닌 div element에는 그런 메서드가 존재하지 않기 때문이다. 

그렇기 때문에 바인딩을 통해 해당 elem, menu의 onclick은 항상 Menu 인스턴스를 가리키도록 bind를 해주어야 한다.

 

new Menu(menu)로 객체를 만들어도 그것을 바인딩해주지 않으면 쓸모가 없다.

 

결론

1. new Menu(menu)로 인스턴스를 만들어도 그게 <div id="menu">와 연결되는 것이 아니며, elem.onclick = this.onClick으로 연결해줘야 <div id="menu">의 onclick에 덮어씌워진다.

2. 그냥 onclick함수만 덮어씌우면 Menu 인스턴스의 save, load, search 같은 다른 함수를 쓸 수 있는 것이 아니므로 .. bind(this)를 통해 onClick 함수의 복사본을 만들고, 이 복사본의 this는 Menu 인스턴스를 가리키도록 한다!

 

 

 

 

이해하고 나면 당연한건데 오해한 게 너무 많아서 돌아오는데 시간이 걸렸다

 

활용하기 2) document에 addEventListener 달아서 counter 구현하기

<input type="button" value="1" data-counter>
<input type="button" value="2" data-counter>

<script>
  document.addEventListener('click', function(event) {

    if (event.target.dataset.counter != undefined) { // 속성이 존재할 경우
      event.target.value++;
    }

  });
</script>

제목 그대로 document에 addEventListener를 단 후에, event.target.dataset.counter 속성이 있는지 없는지 검사해서 값을 처리할 수 있다

 

요약

작동 알고리즘

  1. 컨테이너에 하나의 핸들러를 할당합니다.
  2. 핸들러의 event.target을 사용해 이벤트가 발생한 요소가 어디인지 알아냅니다.
  3. 원하는 요소에서 이벤트가 발생했다고 확인되면 이벤트를 핸들링합니다.

장점

  • 많은 핸들러를 할당하지 않아도 되기 때문에 초기화가 단순해지고 메모리가 절약됩니다.
  • 요소를 추가하거나 제거할 때 해당 요소에 할당된 핸들러를 추가하거나 제거할 필요가 없기 때문에 코드가 짧아집니다.
  • innerHTML이나 유사한 기능을 하는 스크립트로 요소 덩어리를 더하거나 뺄 수 있기 때문에 DOM 수정이 쉬워집니다.

 

단점

  • 이벤트 위임을 사용하려면 이벤트가 반드시 버블링 되어야 합니다. 하지만 몇몇 이벤트는 버블링 되지 않습니다. 그리고 낮은 레벨에 할당한 핸들러엔 event.stopPropagation()를 쓸 수 없습니다.
  • 컨테이너 수준에 할당된 핸들러가 응답할 필요가 있는 이벤트이든 아니든 상관없이 모든 하위 컨테이너에서 발생하는 이벤트에 응답해야 하므로 CPU 작업 부하가 늘어날 수 있습니다. 그런데 이런 부하는 무시할만한 수준이므로 실제로는 잘 고려하지 않습니다.

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

 

이벤트 위임

 

ko.javascript.info