자바스크립트 함수를 다루다 보면 this 키워드를 다뤄야 할 경우가 많이 생기게 됩니다. 이 this 키워드를 정확하게 이해하려면 함수와 메서드의 차이부터 제대로 짚고 넘어가야 됩니다.
함수와 메서드는 둘 다 동일한 함수이지만 존재하는 위치에 따라서 함수가 될 수 있고, 메서드가 될 수 있습니다.
// 함수
const greeting = () => {
console.log(this);
}
const module = {
greeting() {
console.log(this);
}
}
greeting(); // window object
module.greeting(); // module object
차이점이 보이나요? greeting 변수에 할당된 함수에서의 this와 module 객체 내부의 greeting 함수에서의 this는 서로 다른 결과를 보여주고 있죠. 이유는 간단합니다. this 키워드는 현재 자신이 존재하는 부모 객체를 나타내는 키워드랍니다. greeting 변수는 현재 전역 실행 컨텍스트에 존재하므로 this 키워드는 window 객체를 가리키고, module 내부에 있는 greeting 함수는 module 객체 실행 컨텍스트에 존재하기 때문에 this는 module 객체를 가리키고 있는겁니다. 쉽게 이야기하면, 객체 내부에서 생성된 함수가 아니라면 모두 함수라고 표현이 되고, 객체 내부에서 생성된 함수라면 메서드라고 보면 됩니다.
자바스크립트에서 이러한 this 키워드를 잘 제어하기 위해서 어떤 방법을 사용하고 있는지 한번 살펴보도록 하겠습니다.
아래 코드의 결과가 어떻게 나오는지 예상해 보세요. "getName함수는 Module 생성자함수 내부에 존재하니까 getName 함수 내부에 있는 changeName 내부의 this는 Module 생성자함수를 가리킬 것이고, 그럼 parkhoho 문자열을 출력하겠네?" 라고 생각하셨다면 this 키워드에 대한 이해가 부족하신 분이라고 볼 수 있습니다.
function Module(name) {
this.name = name;
}
Module.prototype.getName = function() {
var changeName = function() {
console.log(this); // window
return this.name + 'hoho';
}
return changeName();
}
var module = new Module('park');
console.log(module.getName());
메서드 내부에서 함수를 정의하고, 그 함수 내부에서 this를 사용하면 언뜻보면 this는 부모 객체인 Module을 바라보고 있다고 생각 할 수 있지만 여기서 this는 window 객체를 바라보고 있습니다. 함수를 객체의 메서드 형태로 정의하지 않는 이상 모든 함수는 window 객체를 가리킵니다. 그럼 과거에는 이 this를 처리하려고 한다면 어떤 방식으로 처리를 했을까요? 바로 getName 메서드 내부에 this를 할당해주는 self라는 변수를 정의하고 self 변수로 처리해주는 방식을 사용합니다.
function Module(name) {
this.name = name;
}
Module.prototype.getName = function() {
var self = this;
var changeName = function() {
console.log(self); // Module
return self.name + 'hoho';
}
return changeName();
}
var module = new Module('park');
console.log(module.getName());
정상적으로 저희가 의도한 대로 결과가 나오는 것을 확인할 수 있습니다. 조금 더 세련된 방식으로 해결할 수 있습니다. es6에는 arrow function (화살표 함수)라는 기능이 등장했는데요. 이 기능을 사용하면 굳이 self라는 변수에 this를 할당해서 사용할 필요없이 바로 this를 바인딩해서 사용할 수 있습니다.
function Module(name) {
this.name = name;
}
Module.prototype.getName = function() {
let changeName = () => {
console.log(this); // Module
return this.name + 'hoho';
}
return changeName();
}
const module = new Module('park');
console.log(module.getName());
Function.prototype.call을 활용해서 이 문제를 해결할 수도 있습니다.
function Module(name) {
this.name = name;
}
Module.prototype.getName = function() {
var changeName = function() {
console.log(this);
return this.name + 'hoho';
}
// return changeName.call(this, 1,2,3,4);
// return changeName.apply(this, [1,2,3,4]);
return changeName.call(this);
}
var module = new Module('park');
console.log(module.getName());
call과 apply는 첫 번째 인자의 this를 내부 함수에서 사용할 this로 설정하게 해줍니다. apply 또한 call과 같이 첫 번째 인자로 this를 받는건 똑같지만 뒤에 넘겨줄 값을 [1,2,3,4,5]처럼 배열형태로 넘겨주는 형태를 가지고 있습니다. (call과 같은 경우에는 1,2,3,4,5처럼 배열이 아닌 ,로 전달한다)
call과 apply는 내부 함수에서 사용할 this를 설정하고 함수 실행까지 해주는 반면에 bind와 같은 경우에는 this만 설정해주고 함수 실행은 하지 않는다는 차이점이 있습니다.
function Module(name) {
this.name = name;
}
Module.prototype.getName = function() {
var changeName = function() {
console.log(this);
return this.name + 'hoho';
}
let bindChangeName = changeName.bind(this);
return bindChangeName();
}
var module = new Module('park');
console.log(module.getName());