참고, 실행 context, arrow function
여러 언어들에서 this
는 자기 자신이 속한 객체를 뜻한다.
JavaScript에서의 this
는 다른 언어들과 같이 new 키워드를 통해서 자신을 뜻하도록 할 수는 있지만, 더 다양한 방법으로 사용된다.
조금 더 자세하게 말하면 JavaScript에서 this
는 현재 실행 context이다.(객체와 관련이 깊음)
실행 context란 호출자가 누구냐는 것
과 같은 말로 이해하면 된다.(this가 바라보고 있는 어떤 객체 정도로 이해)
this
는 함수가 호출되는 방식에 따라 동적으로 결정때문에 this 바인딩을 통해 어떤 값과 연결되는지 확인이 가능하다.
바인딩이란 호출 방식에 따라 this가 특정 객체에 연결되는 것
지금부터 this의 4가지 바인딩 동작 방식에 대해 알아보자
일반 함수 실행 방식으로 말 그대로 this
가 전역 객체(window)를 context 객체로 갖는 것이다.
var name = 'red';
function foo () {
console.log(this.name);
}
foo(); // red
전역 스코프에서 정의한 변수들은 전역 객체에 등록된다.
여기서 foo()
처럼 함수를 호출하는 방식을 일반 함수 실행 방식이라고 한다.
따라서 var name = red
은 window.name = red
와 같고 this 객체에 변수를 등록하는 것은 전역 스코프에서 변수를 선언한 것과 같은 의미이다.
다른 예시로
var age = 100;
function foo () {
var age = 99;
bar(age)
}
function bar () {
console.log(this.age);
}
foo();
위 코드에서 bar
함수는 foo
함수 내부에서 일반 함수 실행 방식으로 실행되고 있으므로 bar
함수에 어떤 매개변수를 넘겨주었든 bar
함수 내부의 this.age
는 window.age
를 가리키므로 전역 변수로 선언된 100
이 출력된다.
암시적 바인딩은 어떤 객체를 통해 함수가 호출되면 그 객체가 this의 context 객체가 된다는 뜻이다.
var b = 100;
function test() {
console.log(this.a);
}
var obj = {
a: 20,
func1: test,
func2: function() {
console.log(this.a);
}
};
obj.func1(); // 20
obj.func2(); // 20
위 예시에서 func1
과 func2
는 obj
를 통해 호출되었기 때문에 obj
가 this
가 된다.
따라서 출력을 console.log(this.b)
로 변경한다면 undefined
를 출력하게 된다.
여기서 기본 바인딩과 연관지을 수 있는데
var b = 100;
function test() {
console.log(this.b);
}
var obj = {
a: 20,
func1: test,
func2: function() {
console.log(this.b);
}
};
var obj2 = {
b: 40
};
obj.func1(); // 20
obj.func2(); // 20
에서
var gFunc1 = obj.func1;
gFunc1(); // 100
obj2.func = obj1.func;
obj2.func(); // 40
위 코드를 추가한다면 gFunc1
은 100
이 출력되게 된다.
그 이유는 전역 스코프에서 생성한 변수는 전역 객체에 등록되기 때문에 gFunc1
의 this context는 전역 객체가 되어 전역 변수의 b를 출력할 수 있게 되는 것이다.
그와 반대로 obj2.func
는 obj2
context에서 생성되기 때문에 obj2
의 b 변수값을 출력하게 된다.
함수 객체는call
, apply
, bind
메소드를 가지고 있는데, 이 때 첫 번째 인자로 넘겨주는 것이 this context 객체가 된다.
function test() {
console.log(this);
}
var obj = { name: "yuddomack" };
test.call(obj); // { name: 'yuddomack' }
위 코드에서 test
의 this
는 window가 아닌 obj
가 된다.(이 함수의 this는 이거다라고 명시적으로 알 수 있다!!)
클래스 디자인 패턴 형태로 new
키워드를 통해 바인딩한다.
function foo(a) {
this.a = a;
this.qwer = 20;
}
var bar = new foo(2);
console.log(bar.a); // 2
console.log(bar.qwer); // 20
동작 순서를 보면
1. 새 객체가 만들어진다.
2. 새로 생성된 객체의 프로토타입 체인이 호출 함수의 포로토타입과 연결된다.
3. 생성된 객체를 this context로 명시적으로 사용하여 함수가 실행된다.
4. 생성된 객체를 반환한다.
이 말이 무슨 말이냐면
// 1. 새 객체가 만들어짐
var obj = {};
// 2. 새로 생성된 객체의 Prototype 체인이 함수의 프로토타입과 연결됨
Object.setPrototypeOf(obj, foo.prototype); // 프로토타입을 연결합니다. 이 글에서는 무시해도 상관없습니다.
// 3. 1에서 생성된 객체를 context 객체로 사용(명시적으로)하여 함수가 실행됨
foo.call(obj, 2);
// 4. 이 함수가 객체를 반환하지 않는 한 1에서 생성된 객체가 반환됨
var bar2 = obj; // 여기서 foo는 반환(return)이 없으므로 인스턴스가 생성(된 것처럼 동작)
console.log(bar2.a); // 2
console.log(bar2.qwer); // 20
new
로 객체를 생성하는 척하면서 실제로는 위 코드와 같은 과정이 일어나는 것이다.(위 과정을 new로 한번에!)
한 마디로 새롭게 만들어진 객체를 가리킨다.
화살표 함수에서는 this
가 특별하게 작동한다.
바로 화살표 함수가 선언된 부분 스코프의 this context를 사용하는 것이다.
function objFunction() {
console.log(this.foo); // 13
return {
foo: 25,
bar: () => console.log(this.foo) // 13 vs 25
};
}
objFunction.call({foo: 13}).bar();
bar
가 화살표 함수가 아니였다면 메소드를 소유하고 있는 객체를 가리키기 때문에(암시적 바인딩) 25
가 출력되어야 하지만
화살표 함수는 Lexical this
를 가지기 때문에 상위 환경의 this를 그대로 계승하는 명시적 바인딩과 같이 13
을 출력한다.
이런 특징은 콜백 함수 작성시에 유용하게 사용할 수 있는데 콜백도 함수이기 때문에 es5에서는 this가 전역 객체를 가리키는데 반해 es6에서는 상위 환경의 this를 계승 받게 된다.