오늘은 JS의 this 키워드에 대해서 알아본다.
MDN의 this에 관한 글에선 JS의 this에 대해 이렇게 설명하고 있다.
대부분의 경우 this의 값은 함수를 호출한 방법에 의해 결정됩니다. 실행중에는 할당으로 설정할 수 없고 함수를 호출할 때 마다 다를 수 있습니다.
실행 컨텍스트(global, function 또는 eval)의 프로퍼티는 비엄격 모드에서 항상 객체를 참조하며, 엄격 모드에서는 어떠한 값이든 될 수 있습니다
this의 값은 함수를 호출한 방법에 의해 결정되며, 비엄격 모드의 어떤 실행 컨텍스트이든 항상 this라는 객체를 참조한다라는 설명이다.
전역 문맥에서 this는 전역 객체를 참조한다.
console.log(this); //window
console.log(this === window); //true
var a = 1;
console.log(this.a); //1
console.log(window.a); //1
console.log(this.a === window.a); //true
function myFunc() {
console.log(this);
}
myFunc(); //window
전역 문맥에서 단순히 함수를 호출했을 때 함수 안에서 this는 전역 객체를 참조한다.
함수를 객체의 메서드로 호출했을 때 그 함수 안에서 this는 호출 한 객체를 참조한다.
var obj = {
a: 5,
objFunc: function() {
console.log(this);
}
}
var obj2 = { a: 10 };
obj2.obj2Func = obj.objFunc;
obj.objFunc(); //obj
obj2.obj2Func(); //obj2
var o = {
a: 5, b: 5,
func: function() { return this.a + this.b }
}
var p = Object.create(o);
p.a = 2;
p.b = 2;
console.log(p.func()); //4
console.dir(p);
코드에서 p 객체는 o.prototype.func에 접근하고 있다. o.prototype.func는 this.a와 this.b를 합친 값을 반환한다.
p.func를 실행하면 4를 반환한다. func 메서드를 p가 호출했기 때문에, this는 p를 참조하게 되는 것이다.
객체의 prototype 메서드를 호출했을 때의 this는 객체의 메서드를 호출했을 때의 this와 같다.
var C = function(b) {
this.a = b;
}
C.prototype.a = 5;
var o = new C(7);
console.log(o.a);//7
console.dir(o);
생성자에서 this는 생성자가 반환하는 객체를 받는 객체이다.
설명이 조금 어렵게 된다. 코드를 살펴보자.
결론부터 말하자면 내부함수의 this는 항상 전역객체를 참조한다.
function outerFunc() {
function innerFunc() {
console.log(this);
}
innerFunc();
}
outerFunc();
var o = {
outerFunc: function() {
innerFunc = function() { console.log(this) };
innerFunc();
}
}
o.outerFunc();
Function.prototype의 메서드 중에는 apply, call, bind가 존재한다.
이 세 함수의 공통점은 사용자가 this를 바인딩할 수 있게 해준다는 것이다.
이제 하나하나 알아보겠다.
MDN의 apply 함수에 관한 글에서는 apply에 대해 이렇게 설명하고 있다.
apply() 메서드는 주어진 this 값과 배열 (또는 유사 배열 객체) 로 제공되는 arguments 로 함수를 호출합니다.
apply는 함수를 호출하며 첫 번째 인자로 받은 객체를 호출된 함수에 바인딩하고, 두 번째 인자로 받은 배열(유사 배열도 받는다)을 호출된 함수의 인자로 넣어준다.
코드와 같이 살펴보자.
var o = {
name: "o",
objFunc: function() { console.log(this); }
}
var b = { name: "b" };
o.objFunc.apply(b);
apply의 두 번째 인자도 사용해보자.
var o = {
name: "o",
objFunc: function(name, age) { this.name = name; this.age = age; }
}
var b = { name: "b" };
o.objFunc.apply(b, ["a", 1]);
console.dir(b);
1. o.objFunc는 인자로 name과 age를 받고, 각각 this.name과 this.age의 값으로 할당한다.
2. b라는 별도의 객체를 만든 뒤 objFunc 메서드를 apply 함수로 호출하고 b에 바인딩한다.
3. apply 함수의 두 번째 인자로 넘긴 단일 배열은 차례대로 objFunc의 인자로 들어간다.
헷갈리기 쉬운 것은 apply 함수는 함수를 반환하는 게 아닌 호출한다는 것이다.
call 함수는 apply 함수와 굉장히 닮아있다.
모든 게 apply함수와 똑같지만 단 하나 다른 것은 두 번째 인자로 단일배열을 받는 것이 아니라 인수 목록을 받는다.
예를 들면 apply(this, [1, 2, 3]); 할 것을 call(this, 1, 2, 3); 한다는 것이다.
var o = {
name: "o",
objFunc: function(name, age) { this.name = name; this.age = age; }
}
var b = { name: "b" };
o.objFunc.call(b, "a", 1);
console.dir(b);
bind는 함수를 반환하며 첫 번째 인자로 받은 객체를 해당 함수에 바인드한다. 그리고 두 번째 인자부터 차례대로 해당 함수의 인자로 넣는다. call 함수처럼.
bind 함수에서 주목해야 할 점은 함수를 호출하는 게 아니라 반환한다는 것이다.
var o = {
name: "o",
oFunc: function(name, age) { this.name = name; this.age = age; }
}
var b = { name: "b" };
b.bFunc = o.oFunc.bind(b, "a", 1);
b.bFunc();
console.dir(b);
또한 bind 함수가 반환한 함수는 다음과 같은 내부 프로퍼티를 가지고 있다.
[[TargetFunction]] : bind한 함수의 원본 객체
[[BoundThis]] : bind한 함수를 실행했을 때 첫 번째 인자로 전달 될 객체
[[BoundArgs]] : bind한 함수를 실행했을 때 두 번째 인자부터 전달 될 배열
오늘은 this에 대해서 알아보았다. 알아보며 든 생각은 이건 약간 단순암기같다 라는 생각이었다. 여기에서 호출된 this는 무엇을 참조하고, 저기에서 호출된 this는 무엇을 참조하고.. 이런 느낌.
아무튼 내 머릿속에서 막연히 추상적인 형태로 돌아다니던 개념들이 구체적으로 잡히는 것이 아주 좋은 느낌이다. 여태껏 안 알아보고 뭐 했나 싶은 마음이다.
내일은 고대하던 실행 컨텍스트에 대해 알아볼 차례다. 이전에 실행 컨텍스트에 대해 알려고 했을 때는 실패했었다. 하지만 지금의 나는 다르다... 충분히 알 수 있을 것이다.