실행 컨텍스트에서는 VariableEnvironment와 LexicalEnvironment 뿐만아니라 ThisBinding이 형성된다.
이 ThisBinding은 함수가 호출될 때 결정된다.
즉, 함수가 호출될때에 따라서 this의 값이 정해진다.
이것을 동적으로 바인딩 된다고 부른다.
그래서 상황에 따라 this의 값이 어떻게 설정되는지 정리해보자면 5개로 구분할 수 있다.
1. 전역공간
2. 함수 호출시
3. 메서드 호출시
4. callback 호출시
5. 생성자함수 호출시
그럼 하나하나 어떻게 this값이 변하는지 살펴보자.
우선 전역공간에서의 this는 전역객체를 가리킨다.
즉, Window/global을 가리킨다.
브라우저에서는 Window, node.js에서는 global을 가리킨다.
이유는 전역 컨텍스트를 실행하는 주체가 바로 전역객체이기 때문이다. 즉, 자기 자신을 가리키게 되므로 this의 값은 Window/global이 되는 것이다.
이부분은 조금 이상할 수 있다.
함수안에서 this를 호출해도 전역객체를 가리킨다.
이유는 함수를 실행하는 순간에 얘를 실행 해 주는 주체가 바로 전역 객체이기 때문이다.
function a() {
console.log(this);
}
a();
여기서 a()를 호출해주면 전역공간에서 호출한 것이다.
즉, this를 함수a 의 선언안에서 했지만, 호출은 전역공간에서 호출했기 때문에 this의 값은 전역공간을 가리킨다.
그러나 함수안의 함수 안에서 호출은 한다면 this값은 바로 위의 컨텍스트인 부모함수값을 가리켜야 하지 않을까?
ex1)
function b() { function c(){ console.log(this); } c(); } b();
그런데 실제로는 여기서도 this의 값은 전역객체가 나온다!
이건 자바스크립트 창시자의 버그인지, 아니면 자바스크립트 고유의 특성인지 의견이 분분하다.
그래서 ECMAScript6(ES6)에서는 아예 this 바인딩을 하지 않는 arrow function이란게 나왔다.
이 arrow function은 this의 값을 바로위 컨텍스트를 향한다.
그러나 ECMAScript(ES5)에서는 함수에서의 this는 무조건 전역객체를 가리킨다.
ex2)
var d = { e: function() { function f() { console.log(this); } f(); } } d.e()
이 상황에서는 비록 객체안에 있는 메서드이지만 this를 가지고있는 함수(f())를 호출했기 때문에 묻지도 따지지도 않고 전역객체를 가리킨다.
메서드로 호출 했을 때의 this는 메서드를 호출한 주체이다. 즉, 메서드를 누가 호출했느냐 메소드 명의 '점' 바로 앞에 있는 애가 this를 가리킨다.
ex3)
var a = { b: function() { console.log(this) } } a.b();
여기서는 b호출의 주체가 a이므로 this가 가리키는 값은 a가 된다.
메서드란?
그림에서 b가 함수이지만 a객체에 포함된다.
즉, b함수는 a객체와 관련된 동작을 하므로 b함수는 a객체의 메서드인 것이다.
call,apply,bind에 대한 설명: https://velog.io/@minho100227/call-apply-bind
위와같이 cb()는 함수로서 호출되었으므로 콜백함수더라도 this는 전역객체를 가리킨다.
반면에 call을 이용해서 this를 obj로 가리키게 할 수도 있다.
즉, 매개변수로 넘겨받은 cb를 어떤식으로 처리하느냐에 따라서 this가 달라질 수 있다.
한가지 예를 들어 자세히 살펴보자
여기서 callback()의 this는 별도로 처리하지 않으므로 전역객체를 가리키게 된다.
우리가 원하는 값을 this를 지정하기 위해서는 bind를 쓰면 된다.
만약 this가 obj를 가리키게 하고 싶다면 콜백함수를 호출할때 callback함수의 this값은 obj야 라는 뜻인
callback.bind(obj)를 콜백함수 자리에 넣어주면 된다.
다른예시를 살펴보자
addEventListener의 콜백함수로 this를 출력하면 함수를 실행하는 것이므로 this는 전역객체(window)가 나와야 한다.
그러나 여기서는 a라는 html dom 엘리먼트가 나온다.
이유가 뭘까?
addEventListener라는 함수가 콜백함수를 처리할때 this는 이벤트가 발생한 그 타겟대상 엘리먼트로 하도록 정의가 되어 있기 때문이다.
즉, addEventListener 처럼 this를 별도로 지정해놓은 경우도 얼마든지 있는 것이다.
이런 경우에도 this를 조정하고 싶다면 bind를 이용해서 this의 값을 바꾸어 줄 수 있다.
생성자 함수로 호출시에는 인스턴스 자체가 this가 된다.
위와같이 roy를 new연산자 없이 Person함수를 담아주면 this는 전역객체를 가리키게 된다.
그러므로 window.name, window.age를 출력하면 Person의 매개변수로 들어간 값들이 출력되는 것이다.
new연산자를 이용하면 roy는 인스턴트가 되므로 this를 roy자신을 가리키게 된다.
그러므로 Person안에있는 내용들이 roy라는 객체안에 담기게 되는 것이므로 roy를 출력하면 위와같이 나온다.
var value = 0
var obj = {
value: 1,
setValue: function () {
this.value = 2; // this -> obj
// obj.value = 2
(function () {
this.value = 3 // this -> window
// window.value = 3
})();
}
}
obj.setValue()
console.log(value) // 3
console.log(obj.value) // 2
setValue는 메소드이므로 obj가 this로 된다.
그러나 setValue안에있는 함수는 함수로 실행되었으므로 this는 전역객체를 가리킨다.
그렇다면 함수안에 있어도 this를 부모함수로 가리키게 할 수는 없을까?
var value = 0
var obj = {
value: 1,
setValue: function () {
var self = this
self.value = 2;
(function () {
self.value = 3
})()
}
}
obj.setValue()
console.log(value) // 0
console.log(obj.value) // 0
위에서 self에 this를 담아준다. 여기서 this는 obj를 가리키므로 this -> obj가 된다.
그러므로 setValue안에있는 함수의 self.value는 obj.value를 가리키게 되므로 obj.value는 3이다.
var value = 0
var obj = {
value: 1,
setValue: function () {
this.value = 2;
var a = function () {
this.value = 3
}
a.call(this);
}
}
obj.setValue()
console.log(value) // 0
console.log(obj.value) // 3
또 한가지 방법은 call이다.
call은 this를 정해줄 수 있다.
a.call(this)는 setValue안에 있으므로 this는 obj를 가리키게 된다. call로 인해 this를 obj로 가리키게 했으므로 var a의 this는 obj가 된다.
let value = 0
let obj = {
value : 1,
setValue : function () {
let a = 10;
this.value = 2
{
let a = 20;
this.value = 3
}
console.log(a); // 10
}
}
obj.setValue()
console.log(value) // 0
console.log(obj.value) // 3
더 간단하게 표시하자면 block scope는 this의 영향을 받지 않는다.
이게 무슨말이냐면 함수는 this의 값에 영향을 주지만(this 바인딩)
block scope는 this의 값에 영향을 주지 않는다는 말이다.(this 바인딩x)
그래서 상위의 this를 사용할 수 있는 것이다.
함수: this 바인딩 -> 상위의 this 사용불가
block scope: this 바인딩x -> 상위의 this 사용
반대로 let은 block scope의 영향을 받기 때문에 하위의 a를 사용할 수 없다. 그러므로 a는 10이 출력된다.