"Life is Full of Possibilities" - Soul, 2020

우아한테크코스

[TIL] addEventListener와 this 바인딩 에러

m2ndy 2024. 3. 17. 02:24

 

우아한테크코스 로또 미션을 구현하며 web과 코드 사이의 상호작용을 컨트롤러에서 담당했는데,

그 과정에서 this 바인딩 에러가 발생했다.

 

 

원인을 한 마디로 정리하자면, addEventListener 속성에서의 this 때문!

 

class LottoWebController {
  #webBudget;

  async start() {
    document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget()); // 문제가 발생한 곳
  }

  getWebBudget() {
    const webBudgetInput = document.querySelector("#budget").value;
    this.#webBudget = Number(webBudgetInput);
  }

  /**
   * 예산 입력 관리 및 유효성 검사
   * @param {event} e
   */
  handleWebBudget(event) {
    event.preventDefault();
    this.getWebBudget();
  }
}

 

처음 작성했던 코드에서는 addEventListener의 함수를 화살표 함수 형태가 아닌 메서드를 직접 호출하는 방식을 사용했다.

 

- MDN 참고 : https://developer.mozilla.org/ko/docs/Web/API/EventTarget/addEventListener#%EC%9D%B4%EB%B2%A4%ED%8A%B8_%EC%88%98%EC%8B%A0%EA%B8%B0_%EC%BD%9C%EB%B0%B1

 

 

이때, 해당 id값에서 submit 이벤트가 발생했을 때가 아닌 클래스 인스턴스가 생성되자마자 handleWebBudget이 호출된다.

document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget());
handleWebBudget(event) {
    event.preventDefault();
    this.getWebBudget();
}
getWebBudget() {
    const webBudgetInput = document.querySelector("#budget").value;
    this.#webBudget = Number(webBudgetInput);
}

#budget 에는 값이 입력되지 않은 상태에서 webBudget을 저장하게 된다.

 

 

하지만 #budget에 값을 입력한 뒤 submit을 할 때 webBudget에 값을 저장해야 하는 상황!

 

 

 

# 해결 방법 1

- 화살표 함수 추가

class LottoWebController {
  #webBudget;

  async start() {
    document.querySelector("#content-box-input-budget").addEventListener("submit", () => this.handleWebBudget()); // 화살표 함수
  }

  getWebBudget() {
    const webBudgetInput = document.querySelector("#budget").value;
    this.#webBudget = Number(webBudgetInput);
  }

  /**
   * 예산 입력 관리 및 유효성 검사
   * @param {event} e
   */
  handleWebBudget(event) {
    event.preventDefault();
    this.getWebBudget();
  }
}

 

콜백 함수 방식으로 사용하면 #budget 값을 입력한 뒤 비동기적으로 이벤트를 실행시킬 수 있다

 

 

콜백 함수 - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

콜백 함수는 전달인자로 다른 함수에 전달되는 함수입니다. 이는 일종의 루틴이나 동작을 완료하기 위해 외부 함수 내부에서 호출됩니다.

developer.mozilla.org

 

자바스크립의 콜백 함수 – 자바스크립트에서 콜백 함수가 무엇이고 어떻게 사용하는지 알아

여러분이 프로그래밍과 익숙하시다면, 함수가 무엇이고 어떻게 사용하는지 알고 계실 겁니다. 그러나 콜백 함수가 뭔지 정확히 알고 있나요? 콜백 함수는 자바스크립트에서 중요한 파트 중 하

www.freecodecamp.org

 

 

 

 

 

 

 

# 해결 방법 2

class LottoWebController {
  #webBudget;

  async start() {
    document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind(this)); // 바인딩
  }

  getWebBudget() {
    const webBudgetInput = document.querySelector("#budget").value;
    this.#webBudget = Number(webBudgetInput);
  }

  /**
   * 예산 입력 관리 및 유효성 검사
   * @param {event} e
   */
  handleWebBudget(event) {
    event.preventDefault();
    this.getWebBudget();
  }
}

 

document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind(this));

 

handleWebBudget 위치에 콜백 함수를 사용하지 않고자 한다면, 바인딩을 통해 this를 넘겨주면 된다.

 

document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind());

 

 

만일 위의 경우처럼 앞의 this만 남겨두고 인자로 this를 넘겨주지 않는다면, handleWebBudget 내부의 this.getWebBudget() 메서드가 실행되지 않는다.

 

addEventListener에서의 this는 DOM 요소에서 #content-box-input-budget id 값을 갖는 요소를 의미한다.

즉, 앞의 this를 console로 찍어보면

<input id="content-box-input-budget" /> 

이 출력되게 된다.

 

따라서 bind를 했지만 this를 넘겨주지 않는 경우, handleWebBudget 내부에서의 this는 undefined가 출력된다.

handleWebBudget() {
  console.log(this) // undefined	
}

 

 

 

아래와 같이 바인딩 + this를 넘겨주게 되면, class가 handleWebBudget으로 잘 넘어가게 된다

document.querySelector("#content-box-input-budget").addEventListener("submit", this.handleWebBudget.bind(this));

즉, this.handleWebBudget.bind(this) 문장이 있는 위치에서 this는 class를 의미하는데, 이 class를 handleWebBudget에 전달하겠다는 의미.

 

=> handleWebBudget 내부에서 this를 찍어보면 class가 나온다.

 

handleWebBudget() {
  console.log(this) // class LottoWebController
}

 

 

handleWebBudget에서 this가 잘 바인딩되어 class를 가리키고 있으므로,

this.getWebBudget() 메서드가 정상적으로 실행된다.

handleWebBudget(event) {
  event.preventDefault();
  this.getWebBudget();
}

 

 

 

 

정리

 

정상 동작하는 경우

document.querySelector("#budget").addEventListener("keydown", this.handleWebBudget.bind(this));
  • 앞쪽의 this : 상위 스코프를 가리킴 (여기서는 class)
  • 뒤쪽의 this : class 전달 ⇒ handleWebBudget 메서드에서 class 내의 다른 메서드를 호출해야 하는 경우, this가 class를 가리키게 되어 다른 메서드를 정상적으로 사용할 수 있다 (다른 메서드를 내부에서 호출하지 않는 경우 뒤쪽의 this 생략 가능)
document.querySelector("#budget").addEventListener("keydown", (e) => this.handleWebBudget(e));
  • addEventListener 시 발생하는 event를 메서드에 전달하는 방식
  • 앞쪽의 this는 상위 스코프인 class를 가리킴
  • 뒤쪽의 e는 keydown event를 의미

정상 동작하지 않는 경우

document.querySelector("#budget").addEventListener("keydown", this.handleWebBudget());
  • this : budget id 값을 갖는 DOM 요소를 가리킴
  • keydown을 했을 때 handleWebBudget 이 실행되는 것이 아니라, js 파일이 실행되면서 바로 handleWebBudget 가 실행된다

bind 사용 이유

addEventListener에서 메서드 앞에 붙는 this가 DOM 요소가 아닌 class를 가리키도록 하기 위해

bind에서 this를 인자로 넘겨주는 이유

  • this.handleWebBudget.bind(this) : handleWebBudget 안에서 this가 class가 된다
    • 즉, this.handleWebBudget.bind(this) 문장이 있는 위치에서의 this는 class를 의미하는데, 이 class를 handleWebBudget 에 전달하겠다는 의미
    • 따라서 handleWebBudget 메서드 내부에서 console.log(this) 는 class를 가리킨다
  • this.handleWebBudget.bind() : this로 넘겨주는 인자가 없음
    • handleWebBudget 메서드 내부에서 console.log(this) 는 undefined

 

 

 

 

 

PR 메세지 참고

 

[2단계 - 웹 기반 로또 게임] 프룬(변민지) 미션 제출합니다. by chosim-dvlpr · Pull Request #311 · woowacour

안녕하세요 브콜! 로또 미션 2단계 PR 드립니다. Figma 시안을 보며 작성을 했지만 OS가 달라 원하는 UI가 나오지 않더라구요😭 디자인에서 “지난 주 당첨번호 6개와 보너스 번호 1개를 입력해주세

github.com