서론
아마 대부분의 개발 입문자들은 this
를 제대로 알고 사용하지 않을 것이다.
나 역시 미루고 미루다가 이제야 깊게 공부하고 글을 작성하게 되었다.
배우지 않는다면 this는 아마 자바스크립트에서 가장 어려운 개념이 될 것이라 생각한다.
this가 뭔데..?
이 글에서 여러번 반복할 것 같은 문장이지만, this는 함수를 호출할 때 결정된다.
라는 문장을 기억하며 출발해보자.
this를 정의하자면 함수가 호출되는 방식에 따라 동적으로 결정되는 객체를 가르키는 키워드이다.
쉽게 설명하자면 어떤 녀석이 호출했냐에 따라 this 값이 결정되고, 호출한 녀석을 가르키는 키워드이다.
아직 이해가 안된다면 정상이다.
이해를 돕기 위해 this가 왜 필요한지에 대해 명확하게 짚고 넘어가보자.
모던 자바스크립트 딥다이브의 p.343의 내용이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// this가 없는 세상
// 생성자 함수
function Circle(radius) {
// 자신이 생성될 인스턴스의 식별자를 알 수 없음.
????.radius = radius;
}
// 프로토타입으로 메소드 선언
Circle.prototype.getDiameter = function () {
// 자신이 생성될 인스턴스의 식별자를 알 수 없음.
return 2 * ????.radius;
}
const circle = new Circle(5);
this가 없는 세상을 생각해보자.
생성자 함수가 생성되었고, 당연히 추 후 생성될 인스턴스의 식별자의 값을 알지 못한다.
프로토타입으로 메소드를 정의할때도 마찬가지이다.
즉, this의 존재 가치는 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있게 해주는 것이다.
this의 특징
- 함수가 호출되는 방식에 따라 결정된다.
- 함수, 메서드, 전역 코드 등 모든 곳에 존재한다.
- 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이다.
호출 방식에 따른 this 바인딩
이제부터 짚어볼 부분은 this의 핵심 부분이라고 생각한다.
this는 호출되는 방식에 따라 결정된다고 했고, 당연히 호출되는 방식에 따라 나뉜다.
그렇기 때문에 역시 호출한 녀석
을 주의 깊게 보면 좋을 것 같다.
1.일반 함수 호출 시
this에 전역 객체가 바인딩
된다.
전역 객체는 브라우저 환경에서는 window
객체, Node.js 환경에서는 global
객체 이다.
사실 this는 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있기 때문에, strict mode 적용 시 일반 함수의 this에는 undefined가 바인딩 된다.
재밌는 사실은 window.test()로 메서드로 호출했을때는 this에 전역객체인 window가 바인딩 된다.
JS 개발자 더글라스 크락포드는 함수 호출 시 this 바인딩은 설계상 오류라고 언급했다고 한다.
2.메서드 호출 시
this에 메서드를 호출한 객체
가 바인딩된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const me = {
name: "cho",
whoAmI: function () {
console.log(this);
},
};
// this는 함수가 호출되는 방식에 따라 달라진다.
// 즉, 호출한 놈(객체)가 this가 된다.
// 호출한 놈은 me이다.
me.whoAmI(); // { name: 'cho', whoAmI: [Function: whoAmI] }
const whoAmI = me.whoAmI;
// 호출한 놈은 me가 아니라 전역 객체이다.(앞의 window.가 생략 된 것)
whoAmI(); // 브라우저 의 경우 window, 노드 global
// 호출한 놈은 me가 아니라 btn이다.
var btn = document.getElementById("btn");
btn.addEventListener("click", me.whoAmI); // 버튼 태그가 나온다.
역시 누가 나를 불렀는가..? 에 집중하면 된다.
3.생성자 함수 호출 시
this에 생성자 함수가 생성할 인스턴스
가 바인딩된다.
1
2
3
4
5
6
7
function Circle(radius) {
this.radius = radius;
}
const circle = new Circle(5);
const circle2 = new Circle(10);
console.log(circle); // Circle { radius: 5 }
console.log(circle2); // Circle { radius: 10 }
같은 생성자 함수를 사용해서 인스턴스를 생성했지만 서로 다른 인스턴스가 생성된다.
4.예외적인 상황(bind, call, apply, 화살표 함수 등)
위의 세가지 경우가 this가 바인딩되는 일반적인 경우이다.
하지만 예외적인 경우가 있는데 아래의 경우들이다.
bind, call, apply의 경우
이 세 메서드는 비슷하기 때문에 한꺼번에 설명한다.
1
2
3
// 예외적으로 bind를 사용하면 this를 고정시킬 수 있다.
const bindWhoAmI = me.whoAmI.bind(me);
bindWhoAmI(); // { name: 'cho', whoAmI: [Function: whoAmI] }
- this에
메서드에 첫번째 인자로 전달한 객체를 바인딩
한다.
쉽게 말해 this가 어떤 녀석을 가르킬지 선택할 수 있다.
화살표 함수의 경우
이 녀석이 가장 특이하고, 우리를 괴롭히는 아주 독한 녀석이다.
코드를 보면서 이해해보자.
1
2
3
4
5
6
7
const me = {
name: "cho",
whoAmI: () => {
console.log(this);
},
};
me.whoAmI(); // window
메서드 호출시의 설명했던 코드를 슬쩍 보고 와보자.
whoAmI 함수는 this를 me로 바인딩 했었다.
지금은..? this는 전역 객체인 window를 바인딩 한다. ?????????? 화살표 함수 안의 this는 선언될 때 결정되고, 상위 스코프의 this를 그대로 사용한다
mdn 문서에 의하면 화살표 함수는 this를 바인딩 하지 않는다.
즉, 화살표 함수는 this가 없고, 없기 때문에 상위 스코프의 this를 그대로 사용한다는 말이다.
당장은 이해가 안갈 수 있지만, 이 글은 this에 대한 글이기 때문에 깊게 다루진 않을 것이고,
화살표 함수에 대해서는 따로 포스팅할 예정이다.많관부..!
결론
어제 오늘 JS의 큰 봉우리들을 넘는 것 같다.
어제 배웠던 실행 컨텍스트, 오늘 배웠던 this 모두 제대로 이해하지 못한다면 해결할 수 없는 문제였다고 생각한다.
Reference
- 모던 자바스트립트 Deep Dive
- 프롱트님 유튜브
- mdn - this