컴파일 언어들과는 다르게 JavaScript, TypeScript는 동적으로 this 바인딩이 결정된다.(Dynamic binding). 때문에 원리를 제대로 모르면 오류를 발생시킨다고 하니, 공부하고 이해한대로 정리해보자.
Execution context는 실행 환경이라고 칭하는데, 단어 그대로 자바스크립트 코드가 실행되기 위한 환경에 대한 정보를 의미하며 총 3가지로 이루어져있다. 실행 컨텍스트는 전역에서 시작해, 함수가 호출될 때 스택에 쌓이게 된다.
글로벌 실행 컨텍스트 (Global Execution Context)
단 하나만 정의되는 전역 컨텍스트다. 전역 객체의 정보를 담고있으므로, this는 global object를 참조한다. 브라우저에서는 window, node에서는 module을 의미한다. Call stack에 가장 먼저 추가되고 앱 종료 시 삭제된다.
함수 실행 컨텍스트(Functional Execution Context)
함수가 호출될 때마다 새로운 실행 컨텍스트가 생성되어 스택에 쌓인다.
함수 내부에서의 this는 stric 모드일 때와 아닐 때 다르다. stric 모드에서는 undefined, 아닐 땐 globalThis (전역 객체)를 가리킨다. 함수 스코프내에서 this에 대한 정보가 없기 때문
function thisGame() {
console.log(this);
}
thisGame(); // undefined or globalThis
생성자 함수 or 클래스에서의 this는 앞으로 생성될 인스턴스 자체를 가리킨다.
function Animal(name) {
this.name = name;
this.printName = function () {
console.log(this.name);
};
}
const dog = new Animal('까꿍이')
dog.printName() // '까꿍이'
자바스크립트에서는 누가 호출하냐에 따라서 this가 달라진다. 즉, this를 호출하는 객체 (caller)에 의해 동적으로 결정이 된다. 아래 예시 코드를 참고해보자.
function Red(name) {
this.name = name;
this.printName = function () {
console.log(` 빨강은 바로 ${this.name}`);
};
}
function Blue(name) {
this.name = name;
this.printName = function () {
console.log(` 파랑은 바로 ${this.name}`);
};
}
const red = new Red('빨간색')
const blue = new Blue('파란색')
red.printName() // 빨강은 바로 빨간색
blue.printName() // 파랑은 바로 파란색
// 이런식으로 호출하는 객체에 따라 this가 동적으로 바뀐다.
red.printName = blue.printName;
red.printName() // 파랑은 바로 빨간색
blue.printName() // 파랑은 바로 파란색
// 이게 문제가 될 수 있는 이유??
function printColor(printName) {
printName(); //obj.printName() 처럼 참조하는 객체가 없기 때문에 undefined
}
printColor(blue.printName); // undefined
이 처럼 동적 바인딩이 될 경우, 분명 blue.printName을 전달하여 함수를 실행했음에도 printColor 함수 내에서 printName()의 this는 전역 객체를 가르켜 undefined를 반환한다. 이럴 때 의도치 않은 에러가 발생할 수 있다.
그래서 이를 해결하기 위한 방법은 2가지가 있다.
bind, call, apply 함수를 이용해서 수동적으로 바인딩 해주기.
만들어지는 시점의 객체와 함수가 영원히 바인딩 된다. 하지만 함수를 만들때마다 bind를 써야하기 때문에 불편함이 있다.
let o= {name: "Daniel",
printName: function() {
console.log("내 이름은",this.name);
},
};
o.printName(); // 내 이름은 Daniel
setTimeout(o.printName, 10);// 내 이름은 undefined
setTimeout(o.printName.bind(o), 20); // 내 이름은 Daniel
Arrow 함수를 이용해 정적바인딩을 해주는 방법.
화살표 함수는 만들어지는 그 순간 렉시컬 환경에서의 this를 기억한다. 때문에 화살표 함수 밖에서 제일 근접한 scope의 this를 가리킨다. ( 호출된 함수를 둘러싼 실행 컨텍스트 )
주의
this를 공부하면서 가장 이해가 되지 않았던 부분이니 항상 주의하도록 하자.
const obj = {
name: 'dk',
printName : () => {
console.log(this)
}
}
obj.printName() // 전역객체
화살표 함수 밖에서 가장 근접한 scope의 this를 가르킨다고 배웠기 때문에, obj.printName()을 호출하면 당연히 this는 obj 객체를 반환할거라 예상하고 있었는데... 이게 뭐람?? 전역객체를 반환한다고??... 멘붕이었다.
그래서 이유를 찾아보니 this는 클래스, 생성자함수, 함수 레벨에서만 존재하고, 그 외에는 상위 context의 상위 this를 참조 한다고 하였다. (엘리쌤 감사합니다.) 즉, 블록 레벨 스코프에서는 this가 존재하기 않기 때문에, 객체 리터널 내부에서의 this는 그 상위 this를 참조 한다는 것이다. 때문에 전역객체가 반환 될 수밖에 없었던 것!!
이걸 알고나서 this라는 개념을 그래도 많이 이해하게 된것 같다.
엘리스 SW 트랙 3기 수업자료