자바스크립트와 This 바인딩
신현호
목차
서론
#열심히 우테코 메뉴 문제를 풀던도중, 로직에서 지속적으로 클래스 인스턴스의 필드를 못읽어오는 문제가 발생했습니다.
당시 상황이 A 라는 클래스 인스턴스의 메소드를 B 라는 클래스 인스턴스 메소드로 넘기는 상황이었습니다.
그럼, 해당 파트가 어디가 잘못되었는지 알아볼까요?
문제
#js
calculateResults() {
return this.#coaches.selectRandomMenus(
this.#recommandationMachine.selectCategory,
this.#recommandationMachine.selectRandomMenus
);
}
selectCategory 와 selectRandomMenus 는 클래스 인스턴스의 메소드입니다.
뭐가 잘못된 지 아시겠나요? 자바스크립트를 많이 사용해보신 분들이라면 바로 아셨을 수도 있습니다!!
이유
#문제는 바로 메서드를 전달하는 과정에 있습니다.
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 에서 사용하려 하는 순간,
전달된 selectCategory 와 randomMenuSelect 는 this 컨택스트를 잃어버리게 됩니다.
컨택스트를 잃었기 때문에 this 로 지목된 객체는 참조하고자 했던 클래스 인스턴스의 필드를 참조할 수 없게 된 것이죠.
this가 컨택스트를 잃은 이유
#자바스크립트에서의 this 는 한 가지의 기능만 수행하지 않기 때문입니다. Java 의 그것을 생각하고 오셨다면 혼란스러운 부분이기도 합니다.
자바스크립트에서 this 는 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 바인딩할 객체가 동적으로 결정됩니다.
- 클래식한 함수 호출
js
const bar = function () {
console.log(this)
}
bar() // window
- 메소드 호출
js
const bar = {
a() {
console.log(this)
},
}
bar.a() // bar
- 생성자 함수 호출
js
class bar {
constructor() {
console.log(this)
}
}
const instance = new bar() // instance
- apply / call / bind 호출
이 경우에는 인자로 적은 객체가 됩니다.
해결
#js
calculateResults() {
return this.#coaches.selectRandomMenus(
(dislikeMenus) => this.#recommandationMachine.selectCategory(dislikeMenus),
(dislikeMenus) => this.#recommandationMachine.selectRandomMenus(dislikeMenus)
);
}
결국 전달하는 방식이 잘못되어서 예상했던 객체로 바인딩 된게 아니고 전역객체인 window 에 바인딩되며 오류가 발생한 것입니다.
위의 4번 방법을 적용하여 bind() 를 사용하는 방법도 있겠지만, 가장 간단한 방법은 바로 화살함수 를 사용하는 것입니다.
화살함수 를 사용하면 this 바인딩에서 자유로울 수 있기 때문에 이런 상황에서 사용하시는 것을 추천드립니다.
하지만, 그렇다고 화살함수 로 일반 함수를 모두 대체해야해야만 하는 것은 아닙니다. 화살함수 는 일반함수의 대체제가 아니거든요.
해당 내용은 제가 이전에 정리해두었던 내용이 있어 첨부해드립니다.
참고 자료
#신현호
Frontend Developer
프론트엔드 개발자를 꿈꾸고 있는 대학생입니다. 끊임없이 배우고 성장하는 개발자가 되기 위해 노력하고 있습니다.