[코어 자바스크립트] 03. this
이 글은 코어 자바스크립트 책의 3장(this)을 공부한 내용이다.
다른 대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 의미한다. 자바스크립트에서의 this는 어디서든 사용할 수 있어서 상황에 따라 this가 바라보는 대상이 달라진다. 따라서 정확한 작동 방식을 이해해야 this가 가리키는 대상을 정확하게 예측할 수 있다.
함수와 객체(메서드)의 구분이 느슨한 자바스크립트에서 this는 실질적으로 이 둘을 구분하는 거의 유일한 기능이다.
상황별로 this가 어떻게 달라지는지, 왜 그렇게 되는지, 예상과 다른 대상을 바라보고 있을 경우 그 원인을 효과적으로 추적하는 방법을 살펴보자.
상황에 따라 달라지는 this
자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다. (참고: 02. 실행 컨텍스트)
실행 컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정된다고 할 수 있다. 즉, 함수를 어떤 방식으로 호출하느냐에 따라 값이 달라진다.
(1) 전역 공간에서의 this
전역 공간에서 this는 전역 객체를 가리킨다. 개념상 전역 컨텍스트를 생성하는 주체가 전역 객체이기 때문이다.
전역 객체는 자바스크립트 런타임 환경에 따라 다른 이름과 정보를 가지고 있다.
- 브라우저 환경 : window
- Node.js 환경 : global
(2) 메서드로서 호출할 때 그 메서드 내부에서의 this
어떤 함수를 실행하는 방법 중 가장 일반적인 두 가지 방법은 (1)함수로서 호출하는 경우와 (2)메서드로서 호출하는 경우이다. 이 둘을 구분하는 유일한 차이는 독립성에 있다.
함수는 그 제처로 독립적인 기능을 수행하는 반면, 메서드는 자신을 호출한 대상 객체에 관한 동작을 수행한다.
var func = function (x) {
console.log(this, x);
};
var obj = {
method: func,
};
// 함수로서 호출
func(1); // 출력 : window {...} 1 -> this로 전역객체 출력
// 메서드로서 호출
obj.method(2); // 출력 : {method: f} 2 -> this로 obj 출력
함수로서 호출과 메서드로서 호출은 함수 앞에 점(.)이 있는지 여부만으로 간단하게 구분할 수 있다. (대괄호 표기법도 있다.)
점 표기법이든 대괄호 표기법이든 어떤 함수를 호출할 때 그 함수 이름(프로퍼티명) 앞에 객체가 명시돼 있는 경우에는 메서드로 호출한 것이고, 그렇지 않은 모든 경우에는 함수로 호출한 것이다.
this에는 호출한 주체의 정보가 담긴다. 어떤 함수를 메서드로서 호출하는 경우, 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체이다. 즉, 점 표기법의 경우 마지막 점 앞에 명시된 객체가 곧 this가 된다.
(3) 함수로서 호출할 때 그 함수 내부에서의 this
함수 내부에서의 this
어떤 함수를 함수로서 호출할 경우에는 this가 지정되지 않는다. this에는 호출한 주체의 정보가 담기는데, 함수로서 호출하는 것은 호출 주체(객체지향 언어에서의 객체)를 명시하지 않기 때문이다.
this가 지정되지 않을 경우, this는 전역객체를 발라본다. 따라서 함수에서의 this는 전역객체를 가리킨다.
메서드의 내부함수에서의 this
내부함수 역시 이를 함수로서 호출했는지 메서드로서 호출했는지만 파악하면 this의 값을 정확히 맞출 수 있다.
- 함수로서 호출 : this는 전역 객체를 가리킨다.
- 메서드로서 호출 : this는 함수명(프로퍼티명) 앞의 객체를 가리킨다.
즉, this 바인딩에 관해서는 함수를 실행하는 당시의 주변 환경(메서드 내부인지, 함수 내부인지 등..)은 중요하지 않고, 오직 해당 함수를 호출하는 구문 앞에 점 또는 대괄호 표기가 있는지 없는지가 관건이다.
this를 바인딩하지 않는 함수
ES6에서는 함수 내부에서 this가 전역객체를 바라보는 문제를 보완하고자, this를 바인딩하지 않는 화살표 함수(arrow function)를 새로 도입했다. 화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠지게 되어, 상위 스코프의 this를 그대로 활용할 수 있다.
var obj = {
outer: function () {
console.log(this);
var inner = () => {
console.log(this);
};
inner(); // 출력 : {outer : f} -> this로 obj(상위 스코프의 this) 출력
},
};
obj.outer(); // 출력 : {outer : f} -> this로 obj 출력
(4) 콜백 함수 호출 시 그 함수 내부에서의 this
함수 A의 제어권을 다른 함수(또는 메서드) B에게 넘겨주는 경우 함수 A를 콜백 함수라 한다. 이때 함수 A는 함수 B의 내부 로직에 따라 실행되며, this 역시 함수 B 내부 로직에서 정한 규칙에 따라 값이 결정된다.
콜백 함수도 함수이기 때문에 기본적으로 this가 전역객체를 참조하지만, 제어권을 받은 함수에서 콜백 함수에 별도로 this가 될 대상을 지정한 경우에는 그 대상을 참조하게 된다.
(5) 생성자 함수 내부에서의 this
생성자 함수는 어떤 공통된 성질을 지니는 객체들을 생성하는 데 사용하는 함수이다.
생성자는 구체적인 인스턴스(클래스를 통해 만든 객체)를 만들기 위한 일종의 틀이다. 이 틀에는 해당 클래스의 공통 속성들이 미리 준비돼 있고, 여기에 구체적인 인스턴스의 개성을 더해 개별 인스턴스를 만들 수 있다.
자바스크립트는 함수에 생성자로서의 역할을 함께 부여했다. new 명령어와 함께 함수를 호출하면 해당 함수가 생성자로서 동작하게 된다. 그리고 어떤 함수가 생성자 함수로서 호출된 경우, 내부에서의 this는 곧 새로 만들 구체적인 인스턴스 자신이 된다.
명시적으로 this를 바인딩하는 방법
(1) call, apply 메서드
this를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.
call
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
call 메서드는 메서드의 호출 주체인 함수를 즉시 실행하도록 하는 명령이다. 이때 call 메서드의 첫 번째 인자를 this로 바인딩하고, 이후의 인자들을 호출할 함수의 매개변수로 한다.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func(1, 2, 3); // 출력 : whindow{...} 1, 2, 3
func.call({ x: 1 }, 4, 5, 6); // 출력 : {x:1} 4, 5, 6
apply
Function.prototype.apply(thisArg[, argsArray])
apply 메서드는 call 메서드와 기능적으로 완전히 동일하다. 다만, apply 메서드는 두 번째 인자를 배열로 받아 그 배열의 요소들을 호출할 함수의 매개변수로 지정한다는 점에서 차이가 있다.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
func(1, 2, 3); // 출력 : whindow{...} 1, 2, 3
func.apply({ x: 1 }, [4, 5, 6]); // 출력 : {x:1} 4, 5, 6
(2) bind 메서드
this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.
Function.prototype.bind(thisArg[, arg1[, arg2[, ...]]])
call과 비슷하지만 즉시 호출하지는 않고 넘겨 받은 this 및 인수들을 바탕으로 새로운 함수를 반환하기만 하는 메서드이다.
var func = function (a, b, c) {
console.log(this, a, b, c);
};
var bindFunc = func.bind({ x: 1 });
func(1, 2, 3); // 출력 : whindow{...} 1, 2, 3
bindFunc(4, 5, 6); // 출력 : {x:1} 4, 5, 6
(3) 별로의 인자로 this를 받는 경우 (콜백 함수 내에서의 this)
요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 ths를 받기도 한다.
이런 형태는 여러 내부 요소에 대해 같은 동작을 반복 수행해야 하는 배열 메서드에 많이 포진돼 있고, ES6에 새로 등장한 Set, Map 등의 메서드에도 일부 존재한다.