기술면접을 준비하는 과정에서 자바스크립트의 this 개념에 대해 공부하였고 this 개념에 대해 정리한다. 이 정리글은 코어 자바스크립트(정재남)를 참고하여 작성하였다.
자바스크립트에서 this는 상황에 따라 바라보는 대상이 달라진다.
this는 함수를 호출할 때 결정된다.
함수 호출 방식에 따라 this가 달라진다.
자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 결정된다. 실행 컨텍스트는 함수 호출 시 생성되므로, this는 함수를 호출할 때 결정된다고 할 수 있다.
전역 공간에서 this는 전역 객체를 가리킨다.
메서드로서 호출할 때 this는 메서드 호출 주체(메서드명 앞의 객체)를 참조한다.
어떤 함수를 객체의 프로퍼티에 할당한다고 무조건 메서드가 되는 것은 아니다. 객체의 메서드로서 호출하는 경우에만 메서드로 동작하고 그렇지 않으면 함수로 동작한다.
var func = function (x) {
console.log(this, x);
};
func(1); // Window {...} 1
var obj = {
method: func,
};
obj.method(2); // { method: ƒ func() } 2
obj의 method에 할당한 함수와 func에 할당한 함수는 같은 함수다. 원래의 익명함수는 그대로지만, 이를 변수에 담아 호출한 경우와 객체의 프로퍼티에 할당해 호출한 경우 this는 달라진다.
'함수로서 호출'과 '메서드로서 호출'은 함수 앞에 점(.)의 존재 여부로 간단하게 구분할 수 있다.
결국 어떤 함수를 호출할 때 함수 이름 앞에 객체가 명시되어 있는 경우에는 메서드로서 호출한 것이다. 그렇지 않으면 모두 함수로 호출한 것이다.
this에는 호출 주체에 대한 정보가 담긴다.
어떤 함수를 메서드로서 호출하는 경우, 호출 주체는 바로 함수명(프로퍼티명) 앞의 객체다.
var obj = {
methodA: function () {
console.log(this);
},
inner: {
methodB: function () {
console.log(this);
}
}
};
obj.methodA();
/*
{
methodA: ƒ methodA(),
inner: { methodB: ƒ methodB() }
}
(=== obj)
*/
obj.inner.methodB(); // { methodB: ƒ methodB() } (=== obj.inner)
함수로서 호출한 함수 내부에서의 this는 전역 객체를 가리킨다.
어떤 함수를 함수로서 호출한 경우 this가 지정되지 않는다. this에는 함수 호출 주체에 대한 정보가 담기는데, 함수로서 호출한 경우 호출 주체를 명시하지 않으므로 호출 주체를 알 수 없기 때문이다.
실행 컨텍스트 활성화 당시 this가 지정되지 않으면 결국 this는 전역 객체를 참조하게 된다. 따라서 함수로서 호출한 함수 내부에서의 this는 전역 객체를 가리킨다.
메서드의 내부함수 역시 함수로서 호출하는지 메서드로서 호출하는지를 파악하면 this를 알 수 있다.
var obj1 = {
outer: function () {
console.log(this);
var innerFunc = function () {
console.log(this);
};
innerFunc(); // Window
var obj2 = {
innerMethod: innerFunc
};
obj2.innerMethod(); // { innerMethod: ƒ innerFunc() } (=== obj2)
}
};
obj1.outer(); // { outer: ƒ outer() } (=== obj1)
outer 메서드의 내부함수 innerFunc를 함수로서 호출했을 때와 메서드로서 호출했을 때의 this 바인딩 결과가 다르다. 같은 함수이지만 this 바인딩 결과가 다르다.
메서드의 내부함수를 호출 주체 없이 호출하는 경우에도 자동으로 호출 당시 주변 환경의 this를 그대로 사용하기 위해 변수를 활용하는 우회 방법이 있다.
var obj1 = {
outer: function () {
console.log(this);
var innerFunc1 = function () {
console.log(this);
};
innerFunc1(); // Window
var self = this;
var innerFunc2 = function () {
console.log(self);
}
innerFunc2(); // { outer: ƒ outer() } (=== obj)
}
};
obj1.outer(); // { outer: ƒ outer() } (=== obj1)
화살표 함수는 실행 컨텍스트를 생성할 때 this 바인딩 과정 자체가 빠져서 상위 스코프의 this를 그대로 활용할 수 있다.
함수 내부에서 this가 전역 객체를 바라보는 문제를 보완하기 위해 ES6에서 this를 바인딩하지 않는 화살표 함수를 도입하였다.
var obj = {
outer: function () {
console.log(this);
var innerFunc = () => {
console.log(this);
};
innerFunc(); // { outer: ƒ outer() } (=== obj)
}
};
obj.outer(); // { outer: ƒ outer() } (=== obj)
콜백 함수 내부에서의 this는 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의하지 않은 경우에는 전역 객체를 참조한다.
콜백 함수도 함수이므로 기본적으로 this가 전역 객체를 참조한다. 하지만 콜백 함수의 제어권을 받은 함수가 콜백 함수에 별도로 this가 될 대상을 지정하면 콜백 함수의 this가 그 대상을 참조한다.
setTimeout(function () { // Window
console.log(this);
}, 300);
[1,2,3,4,5].forEach(function (x) { // Window
console.log(this, x);
})
document.body.innerHTML += '<button>클릭</button>';
document.body.querySelector('#a')
.addEventListener('click', function (e) {
console.log(this, e);
});
콜백 함수의 경우, 콜백 함수의 제어권을 가지는 함수가 콜백 함수에서의 this가 무엇을 참조할지를 결정한다. 결정하지 않으면 기본적으로 함수와 마찬가지로 전역 객체를 참조한다.
생성자 함수에서의 this는 생성될 인스턴스를 참조한다.
자바스크립트는 함수에 생성자 역할을 부여했다.
인스턴스 생성 과정
__proto__ 프로퍼티가 있는 객체(인스턴스)를 만든다.var Cat = function (name, age) {
this.name = name;
this.age = age;
};
var choco = new Cat('choco', 5);
var nabi = new Cat('nabi', 3);
console.log(choco);
console.log(nabi);
/*
Cat {
name: 'choco',
age: 5,
__proto__: { constructor: ƒ Cat() }
}
Cat {
name: 'nabi',
age: 3,
__proto__: { constructor: ƒ Cat() }
}
*/