자바스크립트에서 this
는 기본적으로 실행 컨텍스트가 생성될 때 결정된다. 실행 컨텍스트는 함수를 호출할 때 만들어지므로, 다시 말해 this
는 함수를 호출할 때 결정된다고 할 수 있다.
전역 공간에서의 this
는 전역 객체를 가리킨다.
브라우저 환경에서의 전역 객체는 window
객체를 말하고 Node.js 환경에서의 전역 객체는 global
객체를 의미한다.
var
키워드를 사용하여 전역 변수를 생성하게 되면 자바스크립트 엔진은 이를 전역 객체의 프로퍼티로 할당한다. 이는 자바스크립트의 모든 변수는 사실 특정 객체의 프로퍼티로서 동작하기 때문이다.
여기서 말하는 특정 객체가 바로 실행 컨텍스트의 LexicalEnvironment
가 된다.
앞서 var
로 선언한 변수는 전역 객체의 프로퍼티로 할당이 된다고 했다.
다시 말해, var
로 선언한 변수는 window
객체에 할당이 된다는 것이고 이는 곧 a
와 window.a
가 같은 같은 가리킨다는 것을 알 수 있다.
그리고 window.a
에 값을 할당하는 것은 var
키워드로 값을 할당한 것과 동일한 기능을 한다는 것 또한 알 수 있다.
이처럼 대부분의 경우 window
에 직접 할당하는 것과 var
키워드로 할당하는 것이 동일하게 동작하지만 '삭제' 명령에 대해서는 전혀 다르게 동작한다.
삭제 동작의 경우 전역 객체의 프로퍼티에 할당된 값은 삭제가 되지만 전역 변수로 선언한 경우는 삭제가 되지 않는다.
이런 차이가 발생하는 이유는 전역 변수를 선언할 때 자바스크립트 엔진이 전역 객체의 프로퍼티로 값을 할당할 때 configurable
속성(변경 및 삭제 가능성)을 false
로 정의하기 때문이다.
함수를 호출하는 방식은 "함수로서 호출"과 "메서드로서 호출" 두 가지 방식이 존재한다.
이 둘의 차이는 아래와 같이 this
가 가리키는 대상이 달라지게 된다.
var foo = function(x) {
console.log(this, x); // Window {...} 1
};
foo(1);
var obj = {
method: func
};
obj.method(2); // {methof: f} 2
함수로서 호출과 메서드로서 호출을 구분하는 가장 간단한 방식은 함수 앞에 점(.)이 있는지 여부를 확인하는 것이다.
다시 말해, 점(.)이 있다면 객체의 메서드로서 호출하는 것이고 없다면 함수로서 호출하는 것이다.
함수 내부에서 바인딩되는 this를 판별할 때는 함수의 주변 환경(메서드 내부인지, 함수 내부인지 등)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대활호 표기가 있는지 없는지가 관건이 된다.
this를 바인딩하지 않는 함수
ES5까지는 호출 주체가 없는 경우 자동으로 전역 객체가 바인딩 되어서 이를 우회하는 방식으로 아래와 같이 this를 변수에 저장하는 방법으로 사용하곤 했다.
var obj = {
outer: function() {
console.log(this); // {outer: f}
var innerFunc1 = function() {
console.log(this); // Window {...}
};
innerFunc1();
var self = this;
var innerFunc2 = function() {
console.log(this); // {outer: f}
};
innerFunc2();
}
};
obj.outer();
ES6에서는 이러한 방식에서 벗어나 this
를 바인딩하지 않는 화살표 함수가 나왔다. 화살표 함수는 this
를 바인딩하는 과정 자체가 생략되어 자동으로 상위 스코프의 this
를 상속받아 활용할 수 있게 됐다.
Call
메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다.
첫 번째 인자로 this
를 바인딩하고, 이후의 인자들을 호출 함수의 매개변수로 한다.
함수를 그냥 실행하면 this
는 전역 개체를 참조하지만 call
메서드를 사용하면 임의로 this
에 바인딩할 객체를 지정해줄 수 있다.
var func = function(a, b, c){
console.log(this, a, b, c);
};
func(1, 2, 3); // Window {...} 1 2 3
func.call({x: 1}, 4, 5, 6); // {x: 1} 4 5 6
메서드도 마찬가지로 그냥 호출하면 this
는 메서드를 호출한 객체를 가리키지만 call
메서드를 이용해 임의로 this
에 바인딩할 객체를 지정해줄 수 있다.
apply
메서드는 기능적으로 call
메서드와 완벽하게 동일하다.
차이는 call
메서드는 첫 번째 인자를 제외한 모든 인자들을 호출할 함수의 매개변수로 지정하는 반면, apply
메서드는 두 번째 인자로 배열을 받아 해당 배열의 요소들을 호출할 함수의 매개변수로 지정한다.
var func = function(a, b, c) {
console.log(this, a, b, c);
};
func.apply({x: 1}, [4, 5, 6]); // {x: 1} 4, 5, 6
var obj = {
a: 1,
method: function(x, y) {
console.log(this.a, x, y);
}
}
obj.method.apply({a: 4}, [5, 6]); // 4, 5, 6
bind
메서드는 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); // Window {...} 1 2 3 4
var bindFunc1 = func.bind({x: 1});
bindFunc(5, 6, 7, 8); // {x: 1} 5 6 7 8
var bindFunc2 = func.bind({x: 1}, 4, 5);
bindFunc2(6, 7); // {x: 1}
다음 규칙은 명시적 this 바인딩이 없는 한 늘 성립한다.
다음은 명시적 this 바인딩이다. 위 규칙에 부합하지 않는 경우에는 다음 내용을 바탕으로 this 를 예측할 수 있다.