프로그래밍-학습기록/Javascript

자바스크립트 클래스 메소드에서 이벤트리스너 콜백 함수를 작성할 때 주의해야 할 this 바인딩

leesche 2021. 3. 10. 23:59

자바스크립트 클래스 안에서 이벤트리스너를 등록하고, 그 콜백 함수 안에서 this를 사용하려면 주의해야 한다.

아래는 board 엘리먼트의 자식 엘리먼트를 모두 선택해 각각에 이벤트리스너를 등록하는 메서드를 구현한 것이다.

export default class BoardHandler{
  #gameManager;
    /*
    ...
    */
  #CLASS_NAME_BOARD_CHILD= ".board__child";
  $allChildren = document.querySelectorAll(this.#CLASS_NAME_BOARD_CHILD);
  #initializeBoardElement() {
    this.$allChildren.forEach(($child) => ($child.textContent = ""));
    this.$allChildren.forEach(($child) =>
      $child.addEventListener(
        "click",
        this.#handleClickBoardSquare
      )
    );
  }
  #handleClickBoardChild() {
        console.log(this); // <td> ... HTML Element
        this.mark() // Error
  }
    mark() {
        /* ... */
    }
    /*
    ...
    */
}

클래스 안에서 메서드를 작성할 때 this는 자연스럽게 해당 클래스의 인스턴스를 가리킨다.

하지만 이벤트리스너의 콜백함수로 호출될 때, 콜백함수 안에서 this는 클래스의 인스턴스를 가리키는 것이 아니다.

이 this는 이벤트가 일어난 HTML 엘리먼트를 가리킨다.

따라서 나는 이 상황을 아래와 같이 해결했다. 이벤트리스너를 부착할 때 콜백함수에 bind메서드로 콜백함수가 호출될 때 this로 사용할 객체로 this(BoardHandler의 인스턴스), 그 다음 인자로 $child 엘리먼트를 전달했다.

이렇게 하면 문제 없이 의도한 대로 잘 실행된다.

export default class BoardHandler{
  #gameManager;
    /*
    ...
    */
  #CLASS_NAME_BOARD_CHILD= ".board__child";
  $allChildren = document.querySelectorAll(this.#CLASS_NAME_BOARD_CHILD);
  #initializeBoardElement() {
    this.$allChildren.forEach(($child) => ($child.textContent = ""));
    this.$allChildren.forEach(($child) =>
      $child.addEventListener(
        "click",
        this.#handleClickBoardSquare.bind(this, $child)
      )
    );
  }
  #handleClickBoardChild($child) {
    const index = $child.id;
    const activePlayer = this.#gameManager.getActivePlayer();
    const type = activePlayer.getType();
    this.mark($child, index, type);
  }
    /*
    ...
    */
}

물론 다른 방법으로 고차함수를 활용할 수도 있다. 하지만 이번 상황에서는 해당 이벤트리스너의 콜백함수가 기억해야 할 객체가 매번 변동되지 않고 일정해서 그럴 필요가 없겠다고 판단했다.