프로그래밍 공부/Javascript

[자바스크립트] 이벤트 리스너 사용 시 this 바인딩 문제

Kevinkb 2021. 11. 19. 23:14

우테코 지하철 노선도 미션 코드 작성 중에 this 바인딩 문제를 발견했다. 평소 'this 바인딩은 쓸 일이 별로 없지 않을까?'라고 생각하며 this 바인딩에 대해 깊게 생각해보지 않았는데 문제를 직접 겪어보니 상당히 당혹스러웠다. 평소에 짜던 코드와 전혀 이질감을 못 느꼈는데 계속 오류가 나서 한참을 헤맸다.

class StationManager {
  constructor() {
    this.station = [];
    this.newListId = 1;
    this.stationList = document.querySelector('#station-add-list');
    this.stationNameInput = document.querySelector('#station-name-input');
  }

  isValid(name) {
    let isContain = false;
    this.station.forEach(item => {
      if (item.name === name) isContain = true;
    });
    return name.length > 1 && !isContain
  }

  giveWarning() {
    alert(`역 이름 조건을 만족하지 않거나 이미 존재하는 역입니다.
    - 역 이름은 두 글자 이상이어야 합니다.`)
    this.clearAll();
  }

  makeTable() {
    const tr = document.createElement('tr');
    const tdName = document.createElement('td');
    const tdWrap = document.createElement('td');
    const tdDelBtn = document.createElement('input');

    this.setTableAttribute(tr, tdName, tdDelBtn);
    tdWrap.append(tdDelBtn);
    tr.append(tdName, tdWrap);
    this.stationList.append(tr);

    tdDelBtn.addEventListener('click', () => {
      this.deleteList(this.newListId);
    });
  }

  setTableAttribute(tr, name, btn) {
    tr.setAttribute('id', this.newListId);
    btn.setAttribute('class', 'delete-list');
    btn.setAttribute('type', 'button');
    btn.setAttribute('value', '삭제');
    name.textContent = this.stationNameInput.value;
  }

  addStation() {
    if (this.isValid(this.stationNameInput.value)) {
      this.makeTable();
      this.station.push({
        id: this.newListId++,
        name: this.stationNameInput.value,
      })
    } else {
      this.giveWarning();
    }
    this.clearAll();
  }

  clearAll() {
    this.stationNameInput.value = '';
  }

  deleteList(id) {

  }
}

const stationManager = new StationManager();
const addBtn = document.querySelector('#station-add-button');

addBtn.addEventListener('click', stationManager.addStation);  // 문제의 코드 !!

상황

Class StationManager 인스턴스를 생성하고 추가 버튼에 addStation 함수를 클릭 이벤트로 등록해준 뒤 역을 추가하려고 버튼을 클릭하니 다음과 같은 오류를 만났다.

'어라? 이상하네?' 하며 this.isValid() 함수를 addStation 함수 안에서 실행시켜봐도

똑같이 오류가 발생했다.

원인

한참을 생각해도 this 문제라고 생각하지 못하며 이상한 부분을 수정해보다가 결국 this를 콘솔에 찍어봤더니

this가 버튼 태그를 참조하고 있었던 것이다... 그래서 찾아보니

이벤트 리스너 안의 this는 addEventListener 함수를 호출할 때 그 함수가 속한 객체의 참조한다.

라는 것을 알 수 있었다.

해결

1. bind 메서드를 사용한 this 바인딩

bind 메서드는 해당 함수 안의 this를 첫 번째 인수로 받은 객체에 바인딩한다.

Object.Function.prototype.bind(obj)

하여 다음과 같이 수정해 문제를 해결할 수 있었다.

addBtn.addEventListener('click', stationManager.addStation.bind(stationManager));

2. 익명 함수 안에서 실행한다.

이벤트 리스너에 익명 함수를 넣고 이 익명 함수 안에서 메서드를 호출하면 정상적으로 this를 해당 메서드를 참조하는 객체에 바인딩된다.

addBtn.addEventListener('click', fucntion (e) {
  stationManager.addStation();
}