자바스크립트와 This 바인딩

user profile img

신현호

JavaScript
Post Thumbnail

목차

    서론

    열심히 우테코 메뉴 문제를 풀던도중, 로직에서 지속적으로 클래스 인스턴스의 필드를 못읽어오는 문제가 발생했습니다.
    당시 상황이 A 라는 클래스 인스턴스의 메소드를 B 라는 클래스 인스턴스 메소드로 넘기는 상황이었습니다.

    그럼, 해당 파트가 어디가 잘못되었는지 알아볼까요?

    문제

    js

    calculateResults() {
      return this.#coaches.selectRandomMenus(
        this.#recommandationMachine.selectCategory,
        this.#recommandationMachine.selectRandomMenus
      );
    }
    

    selectCategoryselectRandomMenus 는 클래스 인스턴스의 메소드입니다.
    뭐가 잘못된 지 아시겠나요? 자바스크립트를 많이 사용해보신 분들이라면 바로 아셨을 수도 있습니다!!

    이유

    문제는 바로 메서드를 전달하는 과정에 있습니다.

    js

    calculateResults() {
      return this.#coaches.selectRandomMenus(
        this.#recommandationMachine.selectCategory, // 문제가 되는 부분 [1]
        this.#recommandationMachine.selectRandomMenus // 문제가 되는 부분 [2]
      );
    }
    

    뭐 물론 이 짧은 로직에 문제가 있으려면 저기밖에는 없긴 합니다만, 이유가 중요하겠죠?
    문제는 바로 자바스크립트의 this binding 에 있습니다.

    무슨 일이 일어났나요?

    js

    calculateResults() {
      return this.#coaches.selectRandomMenus(
        this.#recommandationMachine.selectCategory, // 문제가 되는 부분 [1]
        this.#recommandationMachine.selectRandomMenus // 문제가 되는 부분 [2]
      );
    }
    
    ...
    
    selectRandomMenus(selectCategory, randomMenuSelect) {
      const selectedMenus = [];
      const categories = selectCategory(this.#dislikeMenus); // 문제 발생 [1]
    
      this.#dislikeMenus.forEach((dislikeMenu) => {
        selectedMenus.push(randomMenuSelect(dislikeMenu)); // 문제 발생 [2]
      });
    
      return {
        categories,
        result: this.#names.map((name, idx) => ({
          name,
          menu: selectedMenus[idx],
        })),
      };
    }
    

    calculateResults 에서 selectRandomMenus 를 호출하며 인자로 넣어놓은 함수를 selectRandomMenus 에서 사용하려 하는 순간,
    전달된 selectCategoryrandomMenuSelectthis 컨택스트를 잃어버리게 됩니다.

    컨택스트를 잃었기 때문에 this 로 지목된 객체는 참조하고자 했던 클래스 인스턴스의 필드를 참조할 수 없게 된 것이죠.

    this가 컨택스트를 잃은 이유

    자바스크립트에서의 this 는 한 가지의 기능만 수행하지 않기 때문입니다. Java 의 그것을 생각하고 오셨다면 혼란스러운 부분이기도 합니다.
    자바스크립트에서 this 는 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 바인딩할 객체가 동적으로 결정됩니다.

    1. 클래식한 함수 호출

    js

    const bar = function () {
      console.log(this)
    }
    
    bar() // window
    
    1. 메소드 호출

    js

    const bar = {
      a() {
        console.log(this)
      },
    }
    
    bar.a() // bar
    
    1. 생성자 함수 호출

    js

    class bar {
      constructor() {
        console.log(this)
      }
    }
    
    const instance = new bar() // instance
    
    1. apply / call / bind 호출

    이 경우에는 인자로 적은 객체가 됩니다.

    해결

    js

    calculateResults() {
      return this.#coaches.selectRandomMenus(
        (dislikeMenus) => this.#recommandationMachine.selectCategory(dislikeMenus),
        (dislikeMenus) => this.#recommandationMachine.selectRandomMenus(dislikeMenus)
      );
    }
    

    결국 전달하는 방식이 잘못되어서 예상했던 객체로 바인딩 된게 아니고 전역객체인 window 에 바인딩되며 오류가 발생한 것입니다.
    위의 4번 방법을 적용하여 bind() 를 사용하는 방법도 있겠지만, 가장 간단한 방법은 바로 화살함수 를 사용하는 것입니다.

    화살함수 를 사용하면 this 바인딩에서 자유로울 수 있기 때문에 이런 상황에서 사용하시는 것을 추천드립니다.
    하지만, 그렇다고 화살함수 로 일반 함수를 모두 대체해야해야만 하는 것은 아닙니다. 화살함수 는 일반함수의 대체제가 아니거든요.
    해당 내용은 제가 이전에 정리해두었던 내용이 있어 첨부해드립니다.

    참고 자료

    Profile Image

    신현호

    Frontend Developer

    프론트엔드 개발자를 꿈꾸고 있는 대학생입니다. 끊임없이 배우고 성장하는 개발자가 되기 위해 노력하고 있습니다.