코어자바스크립트(정재남 지음)의 3-1절에서 다룬 this 내용을 리뷰한 페이지입니다.
철이 없었죠, 파이썬만 배우면 다 되는줄 알고 자바스크립트도 모르면서 패기 넘치게 리액트를 배웠다는 자체가. 그러다 리액트의 class형 컴포넌트에서 갑툭튀한 this가 뭔지 몰라 넋을 놔버렸죠. 함수형 컴포넌트로 전향하니 this가 안 나와서 잊어 버리고 있었네요. 이제 잠시 숨을 돌리는 이때, this를 제대로 알고 넘어가야겠어요.
자바스크립트에서 가장 혼란스러운 개념 this
다른 객체지향 언어에서 this는 클래스로 생성된 인스턴스 객체
그러나 자바스크립트에서 this는 상황에 따라 가리키는 대상이 다르다.
자바스크립트에서 this는 함수와 객체를 실질적으로 구분하는 거의 유일한 기능
전역공간에서 this === 전역객체
전역공간에서 this는 전역객체를 가리키고 전역객체는 window이므로, 전역공간에서는 this === window이다. 그리고 자바스크립트의 모든 변수는 실은 특정객체(여기서는 전역객체)의 프로퍼티이므로, 다음과 같은 관계가 성립한다.
var a = 1;
console.log(a, window.a, this.a); // 1 1 1
그리고 전역에서는 var로 선언하는 것과 window의 프로퍼티로 직접 할당하는 것이 같다
var a = 1;
window.b = 2;
console.log(a, window.a, this.a); // 1 1 1
console.log(b, window.b, this.b); // 2 2 2
전역공간에서 함수자체로 호출 할 때, 객체의 메서드로 호출할때 this는 서로 다른 것을 가리킨다.
var func = function (x) {
console.log(this, x);
};
func(1); // window {...} 1
var obj = {
method: func,
};
obj.method(2); // {method: f} 2
함수자체를 호출하면 this는 전역객체인 window를 가리키지만, 객체의 메서드로 호출하면 this는 객체(위의 예에선 obj)를 가리킨다. 다시말해 익명함수 func는 그대로여도,텍스트 변수로 호출한 경우와 객체의 프로퍼티로 호출한 경우 this가 가리키는 대상이 달라진다.
this에는 호출한 대상의 정보가 담긴다. 함수를 메서드로서 호출하는 경우, this는 호출주체인 함수명 바로 앞의 객체를 가리키게 된다.
var obj = {
methodA: function () {
console.log(this);
}
inner: {
methodB: function () {
console.log(this);
}
}
}
obj.methodA(); // { method: f, inner: { ... } } ( === obj)
obj.inner.methodB(); // { method: f } ( === obj.inner)
함수를 함수로서 호출할 때는 this가 지정되지 않는다. this는 호출한 주체의 정보를 가리키는데, 자바스크립트에서는 함수를 함수로 호출하는 경우에는 호출주체가 없다(객체지향언어라면 호출주체는 객체가 된다). 이런 경우에는 함수의 this는 전역객체를 가리키는데, 이를 자바스크립트이 설계오류라고 한다.
바로 앞에서 언급한 설계오류로 인해서 this가 무얼 가리키는지 혼란스러울때가 많다. 하지만 함수를 함수로 호출할 때 this가 가리키는 것과 메서드로 호출할 때 this가 가리키는 것은 다르다는 것을 알고 구분하면 된다.
var obj1 = {
outer: function () {
console.log(this); // this -> obj1
var innerFunc = function () {
console.log(this); // innerFunc()로 호출이면 this -> window, obj2.innerMethod()로 호출하면 this -> obj2
}
innerFunc();
var obj2 = {
innerMethod: innerFunc,
};
obj2.innerMethod();
}
};
obj1.outer();
해설
1. 세번째줄에 나오는 this는 obj1의 메서드로서 호출 되었으므로 obj1를 가리킨다.
2. innerFunc()는 함수로 호출 되었으므로 다섯번째 줄의 this는 전역객체인 window를 가리킨다.
3. obj2.innerFunc()는 obj2의 메서드로서 호출 되었으므로 다섯번째줄의 this는 obj2를 가리킨다.
이렇듯 함수로 호출할 때, 메서드로 호출할 때 this는 서로 다른 것을 가리킨다는 것을 알면 해석 할 수 있게 된다.
그렇지만 이렇게 하는것이 헷갈리거나 전역객체를 바인딩하지 않고 호출 당시 주변환경의 this를 그대로 받게 하고 싶다면 다음과 같이 하면 된다.
var obj = {
outer: function () {
console.log(this); // this -> obj
var innerFunc1 = function () {
console.log(this); // this -> window
}
innerFunc1();
var self = this; // outer 환경의 this를 할당
var innerFunc2 = function () {
console.log(self); // this -> obj
}
innerFunc2();
}
};
obj.outer();
outer 안에서 var self = this로 outer 환경의 this를 할당해 주면 this를 현재 컨텍스트의 this를 바라보게 하는 효과가 생기므로, innerFunc2안의 this가 obj를 가리키게 된다.
ES6부터 추가된 화살표 함수는 this를 바인딩하지 않으므로 상위 스코프의 this를 활용하게 된다. 쉽게 말해서 화살표 함수를 쓰면 this가 전역객체를 가리키지 않게 할 수 있다. 다음의 예시를 보자.
var obj = {
outer: function() {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc();
}
};
obj.outer();
콜백함수의 경우에도 내부의 this는 경우에 따라 가리키는 대상이 다르다. 콜백함수가 호출 될 때 this가 가리킬 대상을 지정하지 않으면 전역객체(window)를 가리키고, 메서드로서 호출이 되면 메서드의 점(.) 앞의 대상을 this로 가리키게 된다.
예시를 보자
setTimeout(function () {
console.log(this);
}, 300); // this를 가리키는 대상을 지정하지 않았으므로 this는 전역객체인 window객체를 가리키게 된다.
[1,2,3,4,5].forEach(function (x) {
console.log(this, x);
}); // this를 가리키는 대상을 지정하지 않았으므로 this는 전역객체인 window객체를 가리키게 된다
document.body.innerHTML += '<button id="a">클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function(e) {
console.log(this, e)
}); // addEventListener 자신이 가리키는 this를 콜백함수가 가리키도록 한다
자바스크립트에서 생성자 함수는 공통된 특징을 지니는 객체(인스턴스)를 생성하는데 사용하는 함수다. 자바스크립트는 함수에 생성자의 역할을 부여하는 것이다. 프로젝트를 해본 사람이라면 한번쯤은 써봤을 new 명령어와 함께 생성자 함수를 호출하면 해당 함수가 생성자로서 동작하게 된다. 다음과 같은 예시가 있다.
const formData = new FormData(); // ForData라는 생성자 함수는 formData라는 인스턴스를 생성한다. 인스턴스 formData는 키:값의 형태로 데이터를 저장할 수 있는 객체다.
formData.append("title", post.title);
formData.append("content", post.content);
...
함수가 생성자함수로서 호출된 경우, 내부의 this는 새로 만들어질 인스턴스를 가리키게 된다. 그러므로 파이썬에서 클래스를 정의할 때 쓰는 self와 this가 유사하다고 보면 되겠다. 구체적인 인스턴스가 만들어지는 과정은 다음과 같다. 생성자함수의 prototype을 참조하는 _ _ proto _ _ 라는 프로퍼티가 있는 인스턴스를 만들고 공통속성, 개성을 해당 인스턴스 즉 this에 부여하면 된다.
코드로 예를 들어보자.
var Cat = function (name, age) {
this.bark = '야옹';
this.name = name;
this.age = age;
};
var choco = new Cat('초코', 7);
var nabi = new Cat('나비', 5);
console.log(choco, nabi);
Cat이란 변수에 할당된 함수 내부에서는 this의 프로퍼티에 인자러 입력된 값을 대입해준다. 이후 new 명령어와 함께 생성자함수 Cat를 호출하면 인자로 입력된 값들이 this가 가리키는 인스턴스들의 프로퍼티로 저장이 된다. 다시말해, var choco = new Cat('초코', 7)에서 this는 choco 인스턴스를 가리키고, 인자로 입력된 값들이 choco 인스턴스의 프로퍼티로 저장이 되는 것이다.