this
this란?
this
는 단어 자체에서 유추할 수 있듯이, 뭔가를 가리키는 대명사다. 이 this가 무엇을 가리키는지 유추하려면 앞뒤 문맥을 알아야 할 것이다. 프로그래밍에서 또한 this는 어떤 컨텍스트(문맥) 내에서 특정 객체나 변수를 가리킨다.
Context is always the value of the "this" keyword which is a reference to the object that owns the currently executing code.
컨텍스트는 항상 현재 실행 중인 코드를 소유한 객체를 가리키는 this 키워드의 값이다.
*
다만 strict모드를 배제하고 정리하였으며 내가 이해한 부분에 한하여 정리한 것이므로 오류가 있을 수 있음
자바스크립트 this의 특징
아무 함수에도 속하지 않은 this는 언제나 window 객체를 참조한다. 그러나 어떤 함수 내부에 포함된 this는 함수가 정의되는 시점이 아니라 실행되는 방식(호출 방법)에 따라 결정된다. 이 정리글은 함수 내부에 정의된 this가 참조할 수 있는 객체 유형에 관해 정리한 것이다.
함수 호출 방법에 따라 this가 참조하는 유형을 분류하자면 4가지 정도다.
- Window 객체 - 디폴트
- Window 객체가 아닌 상위 객체 - 메서드로 호출될 때
- 생성자로부터 생성된 새로운 객체 - new 연산자로 호출될 때
- 정해진 객체 - apply, call, bind로 컨텍스트가 정해질 때
각 유형별로 예시를 살펴보겠다.
Window 객체
기본적으로 함수가 글로벌 컨텍스트 내에서 호출될 때(=아무 함수에도 속하지 않은 범위에서 호출될 때)에는 this는 디폴트로 Window 객체를 가리킨다. 글로벌(전역) 컨텍스트는 자바스크립트 인터프리터가 코드를 실행하기 시작할 때 기본적으로 생기는 컨텍스트로서, 어떠한 함수에도 속하지 않은 범위를 의미한다.
var a = 20;
function gx () {
return this;
}
function fx () {
return this.a;
}
function fy () {
return window.a;
}
console.log(gx()); // window
console.log(fx()); // 20
console.log(fy()); // 20
객체의 메서드로 호출
var frog = {
RUN_SOUND: "POP!!",
run: function() {
return this.RUN_SOUND;
}
}
frog.run(); // returns "POP!!"
var runningFun = frog.run;
runningFun(); // returns "undefined"
언뜻 보면 같은 결과를 리턴해야할 것 같지만 기본적으로 자바스크립트 컨텍스트는 호출될 때에 정해지므로 결과가 다르다. frog.run()
의 this 컨텍스트는 호출될 때에 dot으로 연결된 객체, 즉 frog가 된다. 반면 runningFun
은 호출될 때 글로벌 컨텍스트에서 호출되었으므로 디폴트 값인 window가 this가 되고, window에는 RUN_SOUND라는 프로퍼티가 없으므로 undefined를 리턴한다.
new 연산자로 호출
var Foo = function () {
this.bar = 'baz'
};
var foo = new Foo();
console.log(foo.bar); // baz
new 연산자와 함께 호출된 함수의 this 컨텍스트는 생성자로부터 만들어진 새로운 객체 Foo {}
를 가리킨다.
new 연산자와 화살표 함수
화살표 함수(Arrow function)는 일반적인 함수와 this가 가리키는 값이 다를 수 있다. 일반적으로 화살표 함수는 자신의 상위 함수의 this를 상속 받는다. new 연산자와 함께 호출된 경우에는 일반 함수(arrow 함수가 아닌 function() {}
) 는 자신이 종속된 객체를 가리키고, arrow 함수는 자신이 종속된 인스턴스를 가리킨다.
function Black() {
this.name = 'white';
return {
name: 'black',
call: function() { console.log(this.name + ' called!') }
}
}
const black = new Black();
black.call(); // 'black called!'
function White() {
this.name = 'white';
return {
name: 'black',
call: () => { console.log(this.name + ' called!') }
}
}
const white = new White();
white.call(); // 'white called!'
일반 함수가 new 연산자와 호출된 경우 this는 생성자로부터 반환된 새로운 객체 즉 return으로 반환된 객체를 가리키므로 name이 black이다.
화살표 함수가 new 연산자와 호출된 경우 this는 종속된 인스턴스를 가리키는데, 이는 function White의 body를 가리키므로 name이 white이다.
apply, call, bind
apply와 call 메서드를 사용하면 원하는 실행 컨텍스트를 지정할 수 있다.
var bar = "xo";
var foo = {bar: "xo xo"}
function test () {return this.bar;}
console.log(test()); // xo
console.log(test.call(foo)) // xo xo
console.log(test.apply(foo)) // xo xo
call과 apply를 사용할 때에 첫번째 인자로는 지정하고자 하는 컨텍스트를 넘기고, 두번째 인자로는 함수의 arguments를 넘기면 된다. 위 예시의 경우 test에 패러미터가 없으므로 첫 번째 인자만 foo로 넘겼다.
call과 apply의 차이는 두 번째 인자를 넘길 때에 call은 아규먼트들을 풀어서 넣어야 된다는 것이고 apply는 배열에 담아 넣어야 한다는 차이일 뿐, 기능은 같다.
function user (firstName, lastName, age) { console.log(this) }
user.call(window, 'John', 'Doe', 30); // Window
user.apply(window, ['John', 'Doe', 30]); // Window
bind는 인자로 받은 컨텍스트를 영구적으로 고정한다는 특징을 갖고 있다. 즉 bind 함수로 컨텍스트를 고정하면 그 뒤에 call, apply, bind를 사용하여 다른 컨텍스트를 지정하더라도 최초에 바인드된 컨텍스트가 바뀌지 않는다.
화살표 함수와 bind
위에도 언급하였지만, 화살표 함수(Arrow function)는 일반적인 함수와 this가 가리키는 값이 다를 수 있다. 일반적으로 화살표 함수는 자신의 상위 함수의 this를 상속 받는다.
이 이유는 화살표 함수를 사용하면 자동으로 bind가 되기 때문이다.
x => this.y
는 function (x) {return this.y}.bind(this)
와 같다.
var a = 'global';
var obj = {
method: function () {
return {
a: "inside method",
normal: function() {return this.a;},
arrow: () => this.a
}
},
a: "inside obj"
}
console.log(obj.method().normal()); // inside method
console.log(obj.method().arrow()); // inside obj
일반 함수로 정의된 normal은 자신이 종속된 객체 즉 return된 객체를 가리키게 되고, 화살표함수는 자신의 상위 함수 즉 method의 this인 obj를 가리키게 된다. 상위 함수가 없는 경우 디폴트인 window를 리턴한다.
var obj = {
a: 1,
normal: function() { return this; },
arrow: () => this
}
const {normal, arrow} = obj; // 비구조화 할당(destructing assignment)
normal(); // Window
obj.normal(); // obj
arrow(); // Window
obj.arrow(); // Window
참고
http://ryanmorr.com/understanding-scope-and-context-in-javascript/
https://blog.pragmatists.com/the-many-faces-of-this-in-javascript-5f8be40df52e