this
는 평소엔 딱히 어렵지 않다고 느껴지면서도, 오픈소스 등 남의 코드를 읽을 때에는 걸림돌이 되곤 한다. 이는 내가 this
를 제대로 알지 못하기 때문인 걸로 느껴져서, 좀 더 공부해보기로 했다.
메서드 호출에서의 this
this는 현재 실행 컨텍스트이다. 아래 예시를 보자.
alert(this === window); // (1) 여기서 this는 window 객체, 즉 true
const caller = {
test: function() {
alert(this === window);
},
}
caller.test(); // (2) 여기서 this는 caller 객체, 즉 false
- 참고로, (1)은 함수 호출, (2)는 메서드 호출이라고 한다. 함수와 메서드를 혼용해서 쓰는 경우도 있는데, 이처럼 엄밀히는 다르다.
- 하지만, 실제로 (1)의 케이스를 메서드 호출이라고 볼 수도 있다. 따지고 보면
window.alert(this === window);
와 동일하기 때문이다. - 단, strict(엄격)모드에서는 이 둘을 다르게 보긴 한다. strict일 때에는 함수 호출로 하면
this
가undefined
가 된다.
이번엔 아래 예시를 보자.
const testObj = {
outerFunc: function() {
function innerFunc() {
console.log(this) // window
}
innerFunc()
},
}
testObj.outerFunc()
- innerFunc가 호출될 때에는 아무 컨텍스트도 없다(바인드 되지 않았다). 따라서, 비엄격모드 기준으로
this
는window
가 되는 것이다. 물론 엄격모드에서는undefined
일 것이다. - 반면, outerFunc의
this
는 testObj이라는 객체가 되겠다.
생성자 함수 / 객체 내에서의 this
new
키워드를 통해 만들어지거나 리터럴 표기에 의해 만들어진 객체에서,this
는 해당 객체를 가리킨다.this
는 보통 동적으로 결정되지만, 객체 내 메서드의this
는 정적(Lexical)으로 결정되어 해당 메서드를 갖고 있는 객체가 된다.- 이는 다른 언어에서의 보편적인
this
와 유사하므로 넘어가겠다.
이벤트 리스너에서의 this
- 이벤트 리스너에서
this
는 이벤트를 발생시킨 객체이다.
컨텍스트가 없을 때 this를 정상적으로 쓰는 방법
객체 메서드가 객체 내부가 아닌 다른 곳에 전달되어 호출되면 this
가 사라진다. 콜백함수를 쓰이는 경우, 클릭 이벤트 등으로 함수가 전달되는 등 케이스에서는 기존 this
와의 연결이 끊기는 것이다. 뿐만 아니라, 함수 안에 함수가 존재하는 경우(앞선 예시에서 innerFunc
)에는 this
를 정상적으로 쓸 수 없었다. 어떻게 해결하면 될까?
this를 상수(const)로 저장해두기
- 간혹
this
가 제대로 먹히지 않아, 어찌저찌this
를 예컨대 self(물론 이 이름은 변경 가능)라고 두어 코드를 정상 작동시켰던 경우가 있었다. 그동안 난 원리를 잘 알지 못했으나, 이번에 알게 되었다. -
this
를 상수로 지정했기 때문에 아래와 같이 서브루틴 안에서 이this
를 찾을 수 있게 되는 것이다.(this
가 freezing 되어버리는 느낌)function Family(firstName) { this.firstName = firstName const names = ['Seungwoo', 'Hyunsang', 'Zzanga'] const self = this names.map(function(value, index) { console.log(value + ' ' + self.firstName) }) } const hansFamily = new Family('Han') // Seungwoo Han // Hyunsang Han // Zzanga Han
bind 메서드 쓰기
- 매번 상수화하기는 좀 귀찮을 수 있다. 함수가 겹겹이 있을 수 있고, 그 때마다 상수의 이름을 정하는 것부터가 좀 그렇다.
-
따라서 조금 더 나은 방법으로,
bind
메서드를 써서 서브루틴에 영구적으로this
를 고정시킬 수 있다.function Family(firstName) { this.firstName = firstName const names = ['Seungwoo', 'Hyunsang', 'Zzanga'] names.map(function(value, index) { console.log(value + ' ' + this.firstName) }.bind(this)) } const hansFamily = new Family('Han') // Seungwoo Han // Hyunsang Han // Zzanga Han
- 사실, React.js를 쓰면서 뭔지도 모르고
.bind(this)
를 쓰고 있는 경우가 많을 것이다. 이번 기회에 알고 쓰자.
화살표함수 쓰기
- bind를 많은 사람들이 이해하기 어려워하기 때문에 화살표함수가 더 사랑받는 것도 있는 것 같다.(화살표함수는 곧 따로 다루어보도록 하겠다.)
- 일반적으로
this
는 동적 스코프에 의해 결정되지만, 화살표함수를 썼을 때에는 자신을 둘러싼 정적(Lexical) 스코프에 의해this
가 결정되게 된다. -
<모던 자바스크립트 튜토리얼>이라는 오픈소스 프로젝트의 한글판에 Contribution했을 때 이에 대해 다루면서 좀 더 알게 되었는데,
this
를 bind하는 방법과 이렇게 화살표함수를 쓰는 방법은 엄밀히 말하면 조금 다르다고 한다. bind(this)를 했을 때this
는 아예 한정이 되어버리는 것이고, 화살표함수 안에서의this
는 마치 일반적인 변수가 상위 렉시컬 환경에서 변수의 검색이 이루어지는 것처럼, 상위 렉시컬 환경에서this
를 찾게 되는 것이다.function Family(firstName) { this.firstName = firstName const names = ['Seungwoo', 'Hyunsang', 'Zzanga'] names.map((value, index) => { console.log(value + ' ' + this.firstName) }) } const hansFamily = new Family('Han') // Seungwoo Han // Hyunsang Han // Zzanga Han
- 참고로 이 화살표함수가 포함된 코드를 babel로 트랜스파일링 해보면,
this
를 const를 통해 저장해두는 것과 똑같이 변환됨을 알 수 있다. 그렇다, 화살표함수는 상위 렉시컬 환경에서this
를 찾아 상수화하여 쓰는 방식으로 작동하는 것이다.(다시 말해, 앞서 소개한 ‘this를 상수로 저장해두기’와 ‘bind 메서드 쓰기’는 사실상 같은 거나 다름 없다는 것이다!)
[참고자료]
https://blueshw.github.io/2018/03/12/this/
https://ko.javascript.info/arrow-functions