본 강의는 자바스크립트의 기초를 대충 안다고 가정하고 시작하는 조금 심도 깊은 포스팅이다.
완전 처음부터 배우고 싶다면 다른 블로그나 책의 글을 참조하기를 바란다.
특별한 추가 설명이 없다면 nodejs가 아닌 브라우저에서 사용하는 js를 의미한다.
참고:
javascript docs
ecmascript specification
this를 사용할때 조심해야하는점이 많다.
this는 전에도 말했지만 사실 규칙이 있는데 호출한녀석을 this라고 한다.
문제는 누가 호출했는지가 매우 애매모호하다는 것이다.
그래서 보통은 외우고 넘어가는 면이 있는데 그래서 전시간에는 그냥 보고 외워라고 만들었다.
그러나 실제로 사용하다보면 너무 이상한 상황들 때문에 문제가 생기는 경우가 많다.
그래서 여러분들에게 몇가지 문제가 생길 수 있는 this에 대해서 알려드리도록 하겠다.
var myObj = {
num1: 1,
num2: 20,
outer: function () {
console.log('outer: ' + this);
console.log(this === myObj);
function inner() {
console.log('inner: ' + this);
console.log(this === myObj)
}
inner();
}
};
myObj.outer();
위와 같은 코드를 보자. 과연 어떤 결과가 출력될지 예상이 되는가?
과연 true true가 나올거 같은가?
대부분은 그렇게 생각할 것 같지만 실제로는 그렇지 않다는 것이다.
놀랍게도 inner함수는 그냥 외부함수처럼 작동하게 되는걸 확인할 수 있다.
여러분은 도대체 이게 뭐냐고 반문할 수 있을지도 모른다.
위에 규칙이 있다고 했던걸 기억하는가?
this는 전에도 말했지만 사실 규칙이 있는데 호출한녀석을 this라고 한다.
문제는 누가 호출했는지가 매우 애매모호하다는 것이다.
사실 규칙이 있다고, 누가 호출했는지가 this를 결정한다고 하였다.
그럼 다시 한번 잘 보도록 하자.
myObj의 outer는 당연히 myObj의 메소드이므로 outer가 실행되는 순간에 myObj자신이 this로서 바인딩이 된다.
그러나 inner의 경우에는myObj의 부속품이 아니다. 그러므로 바인딩이 다시 되지 않는다.
바인딩이 되지 않았다면 기본바인딩은 nodejs에서는 global, browser에서는 wiindow, strict에서는 undefiend이다.
그래서 만약 여러분이 원하는 효과를 하려면 outer내부에서 inner를 바인딩 시켜줘야한다.
inner.bind(this)();
이 방식이 자주 쓰일꺼 같지 않다고 생각할 수 있으나 클로저를 사용할때는 이 방법을 거의 무조건 사용한다고 보면된다.
그러므로 매우 주의해서 사용해야한다.
내부함수의 문제 말고도 다른 문제도 있다.
여러분들은 메소드로 작동할 때는 그래도 this가 무조건 지켜지겠지라고 생각할 수 있다.
하지만 정확히 말하면 그것조차도 아니다.
var myObj = {
num1: 1,
num2: 20,
method: function () {
console.log('method: ' + this);
console.log(this === myObj);
}
};
myObj.method();
function test(callback) {
callback();
}
test(myObj.method);
무슨사활문제 마냥 까다로운데 이번에도 이 문제를 풀어보도록 하자.
과연 어떤 결과가 나올까??
이번엔 둘다 true로 나올거라고 생각헀지만 이번에도 마찬가지이다.
메소드를 분리해서 다른 변수로 등록하면 그 변수의 this를 따르게된다.
위의 경우 test는 전역 함수이므로 당연히 this는 window이다.
var myObj = {
num1: 1,
num2: 20,
method: function () {
console.log('method: ' + this);
console.log(this === yourObj);
}
};
var yourObj = {
method: myObj.method
};
yourObj.method();
위의 코드를 보자, 노파심에서 말하는건데 myObj.method에서 this===yourObj로 변하였다.
위의 코드를 여러분은 이제 예상할수 있어야한다.
답은 당연히 true다.
마지막으로 가장 큰 문제점인 arrow 함수에서의 this를 보도록 하겠다.
var myObj = {
num1: 1,
num2: 20,
method: () => {
console.log('method: ' + this);
console.log(this === myObj);
}
};
myObj.method();
위코드의 실행결과가 예상이되는가?
여러분들이 만약 true로 예상했다면 실행해 보는순간 기절 초풍할 것이다.
헐랭... 도대체 뭐냐? 라고생각할 수 있다.
대부분의 사람들이 arrow함수를 그냥 일반함수로 만들면 보기 안좋으니까 깔끔하게보이려는 문법적 설탕쯤으로 생각하는 사람이 많다.
절대 필자 이야기는 아니고.
어쨋든 arrow함수는 일반함수와는 다른 몇가지의 차이점이 있다고 언급했었는데 가장 중요한것은 this의 지정 타이밍이다.
이를 모르면 아~~~주 심각한 에러를 야기할 수 있다.
1.this는 arrow함수를 "생성할" 당시의 this가 된다.
2.한번 지정된 this는 "어떠한" 방법으로도 변경이 불가능하다. 말그대로 어떠한 방법으로도.
여러분들은 의아해 할 수 있을건데 생성할 당시의 this가 된다는 말은 선언되는 순간을 의미한다.
위의 경우 전역에서 myObj의 method를 생성중이므로 당연히 전역 상태의 this가 된다.
만약 this의 값을 바꾸려면 현재 블럭의 this의 주체를 바꿔야한다.
var myObj = {
num1: 1,
num2: 20,
method: function() {
let test = () => {
console.log('method: ' + this);
console.log(this === myObj);
};
return test();
}
};
myObj.method();
위의 같은 코드의 경우 test함수는 method함수의 블럭안에서 정의되고 있다.
이 때의 this는 myObj이므로 계속해서 myObj로 고정된다.
이 부분은 아주아주 아주 중요하다.
여기서 불변성에 주의해야한다. call,apply,bind를 등원해도 바뀌지 않는 다는 점이다.
var myObj = {
num1: 1,
num2: 20,
method: function() {
let test = () => {
console.log('method: ' + this);
console.log(this === myObj);
};
test.bind({});
return test();
}
};
myObj.method();
이 코드를 실행해보면 알겠지만 명시적으로 선언해도 바뀌지 않는다.
arrow함수의 this는 불변이기 때문이다.
이러한 this의 주의점을 충분히 알고 실행하는 것이 매우 중요하다는 것을 알 필요가 있다.