다른 객체지향 언어에서 this는 클래스 인스턴스 객체를 의미한다. 하지만 자바스크립트의 this는 상황에 따라 바라보는 대상이 달라진다.
자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다. 실행 컨텍스트는 함수를 호출할 때 생성되므로, 다시 말하면 this는 함수를 호출할 때 결정된다.
전역 공간에서 this는 전역 객체를 가리킨다. 자바스크립트에서 전역 객체는 런타임 환경에 따라 다른 정보를 가지고 있다. 브라우저 환경의 경우, window, Node.js 런타임에서는 global을 뜻한다.
함수를 실행하는 방법은 여러 가지가 있는데, 일반적으로 함수로서 호출하는 경우와 메서드로서 호출하는 경우 두 가지가 있다. 프로그래밍 언어에서 함수와 메서드는 미리 정의한 동작을 수행하는 코드 뭉치를 의미한다. 이 둘은 구분하는 차이는 독립성인데, 함수는 그 자체로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 대한 동작을 수행한다. 다시 말해 함수를 객체의 메서드로서 호출할 경우에만 메서드로 동작하고, 그렇지 않은 경우 함수로 동작한다.
var func = function (x) {
console.log(this, x);
}
func(1); // global object
var obj = {
method: func
}
obj.method(2); // obj
첫번째 func를 호출하면, this가 전역 객체를 가리킨다. 반면, obj라는 객체의 메서드에 func를 할당하고, obj의 메서드 method를 호출하면 this는 obj를 가리킨다. method 프로퍼티에 할당한 값과 func 변수에 할당한 값은 둘 다 첫 번째 줄에서 선언한 함수를 참조하지만 이를 변수에 담아서 호출하냐, 객체의 프로퍼티로서 호출하냐에 따라 this가 달라진다.
this에는 호출한 주체에 대한 정보가 담긴다. 어떤 함수를 메서드로서 호출하는 경우, 호출 주체는 바로 메서드를 프로퍼티로 갖는 객체가 된다.
중첩 객체의 메서드는 해당 메서드를 프로퍼티로 갖는 가장 가까운 객체가 this가 된다.
var obj = {
inner: {
methodInner: function() {console.log(this)} // { methodInner: [Function: methodInner]}
}
}
어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다. this에는 호출한 주체에 대한 정보가 담기는데, 함수로서 호출하는 것은 호출 주체를 명시하지 않기 때문에 호출 주체의 정보를 알 수 없다.
메서드 내부에서 정의하고 실행한 함수에서의 this 또한 메서드로서 호출했는지, 함수로서 호출했는지에 따라 바뀐다.
var obj1 = {
outer: function() {
console.log(this) // 1. obj1 {outer: [Function: outer]}
var innerFunction = function() {
console.log(this);
}
innerFunction(); // 2. global
var obj2 = {
innerMethod: innerFunction,
}
obj2.innerMethod(); // 3. obj2 {innerMethod: [Fucntion: innerMethod]}
}
}
obj1.outer();
이렇게 this는 호출 방법에 따라 가리키는 대상 객체가 달라진다. 일정한 패턴은 있지만, 워낙 직관적이지 않아 this를 사용하는 데 있어 종종 혼란을 야기할 수 있다. 임시방편으로 허술해 보이긴 하지만, 변수를 활용해서 this를 고정할 수 있다.
var obj ={
outer: function() {
console.log(this); // obj
var innerFunc1 = function() {
console.log(this)
};
innerFunc1(); // global
var self = this;
var innerFunc2 = function() {
console.log(self); //obj
}
innerFunc2();
}
}
obj.outer();
call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다. 이때 call 메서드의 첫 번째 인자를 this 로 바인딩하고, 이 후의 인자들을 호출할 함수의 매개변수로 사용한다. 함수를 그냥 실행하면 this가 전역 객체를 참조하지만, call 메서드를 사용하면 임의의 객체를 this로 지정할 수 있다.
// example 1
var func = function(a, b, c) {
console.log(this, a, b, c);
}
func(1, 2, 3); // global 1 2 3
func.call({lexicalEnv: 'custom lexical environment'}, 4,5,6); // {lexicalEnv: 'custom lexical environment'} 4 5 6
// example 2
var obj = {
a: 1,
method: function(x, y) {
console.log(this, x, y);
}
};
obj.method(2, 3); // {a: 1, method:[Function: method]} 2 3
obj.method.call({a: 100}, 10, 20); // {a: 100} 10 20
apply 메서드는 call 메서드와 기능적으로 완전히 동일하다. call 메서드는 첫 번째 인자들을 제외한 나머지 인자들을 함수의 매개변수로 지정한다. 반면 apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다.
var func = function(a, b, c) {
console.log(this, a, b, c);
}
func.apply({x: 1}, [3,4,5]); // {x: 1} 3 4 5
var obj = {
a: 1,
method: function(x, y) {
console.log(this.a, x, y)
}
}
obj.method(3, 4); // 1 3 4
obj.method.apply({a: 1000}, [10, 20]); // 1000 10 20
call과 비슷하지만, 즉시 함수를 호출하지 않고, 넘겨받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드이다. bind를 통해 만들어진 새로운 함수를 호출할 때 인수를 넘기면 그 인수들은 기존 bind 메서드를 호출할 때 전달했던 인수들에 뒤이어 등록된다. 즉, bind 메서드는 함수에 this를 미치 적용하는 것과 부분 적용 함수를 구현하는 두 가지 목적을 가진다.
var func = function(a, b, c, d) {
console.log(this, a, b, c, d);
}
func(1, 2, 3, 4); // global
var bindFunc1 = func.bind({x: 1});
console.log(bindFunc1); // [Function: bound func] 함수를 반환
bindFunc1(5,6,7,8); // {x: 1} 5 6 7 8
var bindFunc2 = func.bind({x: 1}, 4, 5);
bindFunc2(6, 7); // {x: 1} 4 5 6 7
bindFunc2(8, 9); // {x: 1} 4 5 8 9
bind를 적용 한 뒤 반환된 함수의 name 프로퍼티를 확인해보면, bound라는 단어가 붙는다. 이를 통해 이 함수가 bind라는 메서드를 통해 만들어진 함수고, 그 원본은 func라는 것을 알 수 있다.
화살표 함수는 실행 컨텍스트 생성 시 this를 바인딩하는 과정이 없다. 즉, 화살표 함수 내부에는 this가 아예 없으며, this에 접근하고자 하면, 스코프 체인 상 가장 가까운 this에 접근하게 된다.
var obj = {
a: 10,
outer: function() {
console.log(this); // {a:10, outer: [Function: outer]}
var innerFunc = () => {
console.log(this); // {a:10, outer: [Function: outer]}
}
innerFunc();
}
}
obj.outer();
예전 포스팅에서 화살표 함수의 this는 전역 객체를 가리킨다고 쓴 적이 있는데, 빨리 수정하러 가야겠다;;
명시적 this 바인딩이 없는 경우
명시적으로 this를 바인딩 할 경우