자바스크립트의 예약어인 this에 대해서 알아본다. 자바스크립트의 this는 다른 언어의 this와 다르게 동작하는 방식이 상당히 난해하다. 공부하면서 이해에 어려움을 겪었고, 정리하고 넘어갈 필요성을 느꼈다. this 키워드의 모든걸 한번에 이해할 순 없고 모든 내용을 이 글에 다 적을 수도 없지만 핵심 개념과 중요한 부분을 짚어볼 것이다. 이 글에서는 this의 기본개념 및 사용법과 여러 상황에서 작동하는 방식의 차이를 알아본다.
this는 자바스크립트의 예약어로서 메소드를 소유한 객체를 가리킬 때 사용한다.
함수와 메소드의 차이
자바스크립트에서 함수와 메소드는 다르다. 일반적으로 함수 이름과 인자를 정의해서 호출하는 함수는 그냥 함수이다. 메소드는 함수의 하위 개념으로서 오브젝트 안에서 정의하여 호출한 함수를 자바스크립트에서 메소드라고 부른다.
아래 코드를 보면 객체 안의 메소드에서 해당 객체의 키만으로 값에 접근할 수 없다. 실행하면 오류가 발생한다.
코드
const cat = {
name: 'Blue Steele',
color: 'grey',
breed: 'scottish fold',
breedPrint() {
console.log(breed);
}
}
실행 결과
console.log(breed)
안의 키breed
는 오브젝트와 함께 불러올 순 있지만 변수처럼 단독적으로 쓰일 수 없기 때문이다. 하지만 this를 활용하면 메소드 안에서 키를 이용해 오브젝트 밸류에 접근 가능하다.
코드
const cat = {
name: 'Blue Steele',
color: 'grey',
breed: 'scottish fold',
meow() {
console.log(`This is ${this}`);
}
}
결과
메소드 안의 this 키워드는 메소드를 만든 오브젝트를 가리키게 된다. 위 코드에서 this
를 출력한 결과 object
객체를 가리키고 있음을 알 수 있다.
this는 어떻게 쓰이냐에 따라 바인딩(binding)되는 객체가 다르다. 여러 경우에서 binding이 어떻게 달라지는지 그 차이를 알아보자.
binding이란?
binding은 사전적 의미로는 "묶다"라는 의미로, 두 데이터 혹은 정보의 소스를 일치시키는 기법을 의미한다. 즉this
가 객체를 가리키는 것을 바인딩한다고 할 수 있다.
코드
const cat = {
name: 'Blue Steele',
color: 'grey',
breed: 'scottish fold',
breedPrint() {
console.log("This is: ", this);
}
}
const cat2 = cat.breedPrint;
cat2()
위 코드를 보자. cat
이라는 객체의 breedPrint
라는 함수를 새로운 변수를 만들어 그 변수에 할당한다. 위 코드에서 cat2
의 this
는 무엇을 가리킬까?
결과
바로 window
객체를 가리킨다. 왜냐하면 cat2
는 cat
에서 breedPrint
를 꺼내와서 붙여넣어준 것이기 때문에 더 이상 cat
의 메서드가 아니기 때문이다.
arrow function 안에서 쓰인 this는 일반 메서드 안의 this와는 다르게 작동한다.
코드
const person = {
firstName: 'Viggo',
lastName: 'Mortensen',
fullName: () => {
return `${this.firstName} ${this.lastName}`
},
}
// 화살표 함수는 메서드를 정의하려고 쓰는 것이 아니다. 따라서 화살표 안의 this는 화살표 함수 자체를 가리키진 않는다.
위 코드에서 this
는 무엇을 가리키게 될까? 바로 window객체를 가리킨다. arrow function 안에서 this는 자신을 둘러싸고 있는 코드의 상위 레벨에서의 객체를 가리키게 되기 때문이다.
결과
따라서 결과는 undefined
가 나온다. this
가 자신을 둘러싼 person객체의 상위 객체인 window객체를 가리켰고 window객체에서는 firstName
과 lastName
이라는 변수는 없기 때문이다.
코드
const person = {
firstName: 'Viggo',
lastName: 'Mortensen',
fullName() {
console.log("fullName this: ", this);
return `${this.firstName} ${this.lastName}`
},
shoutName: function() {
setTimeout(() => {
console.log("setTimeout this: ", this);
console.log(this.fullName());
}, 3000)
}
}
위 코드의 결과는 어떻게 될까?
setTimeout
에서 호출한 this
는 자신을 둘러싼 코드 setTimeout
의 상위 레벨 코드 shotName
을 소유한 person
을 가리키게 된다. this.fullName()
으로 호출한 fullName
안의 this
는 this.fullName()
의 this
가 person
객체를 가리키므로 똑같이 person
객체를 가리킨다.
코드
const person = {
firstName: 'Viggo',
lastName: 'Mortensen',
fullName: () => {
console.log("fullName this: ", this);
return `${this.firstName} ${this.lastName}`
},
shoutName: function() {
setTimeout(() => {
console.log("setTimeout this: ", this);
console.log(this.fullName());
}, 3000)
}
}
fullName
메서드 또한 arrow function으로 정의돼 있으므로 setTimeout
에서 호출한 fullName
안의 this
는 자신을 둘러싼 코드의 상위 레벨 객체인 window객체를 가리킨다.
결과
this를 명시적으로 바인딩하여 this가 가리키는 객체를 바꿔줄 수 있다. 이때 사용할 수 있는 함수가 call
, apply
, bind
이다.
call() 함수는 함수객체에 미리 정의되어 있는 함수로서 첫번째 인자로 객체를 주는데 call()을 호출하는 메서드가 인자로 주어진 객체에 선언돼 있는 메서드처럼 동작하게 한다. 두번째 인자부터 호출한 메서드의 인자를 넣어주면 된다.
코드
let person1 = {
name: 'Jo'
};
let person2 = {
name: 'Kim',
study: function() {
console.log(this.name + '이/가 공부를 하고 있습니다.');
}
};
person2.study();
person2.study.call(person1);
결과
call
을 호출하는 study
함수가 person1
객체의 메서드처럼 동작하게 만들었다.
apply도 call과 마찬가지로 this가 가리키는 객체를 바꿔주고 메서드를 실행한다. 차이점은 두번째 인자로 배열을 받아 호출한 메서드의 인자로 넣어준다.
코드
const obj = { name: 'Tom'};
const say = function(city) {
console.log(`Hello, my name is ${this.name}, I live in ${city}`);
};
say.apply(obj, ["seoul"]);
결과
bind는 call, apply 와 다르게 함수를 실행하지 않는다. 대신 함수를 지정한 객체를 가리키도록 바꾼 bound 함수를 리턴한다.
코드
const obj = { name: 'Tom'};
const say = function(city) {
console.log(`Hello, my name is ${this.name}, I live in ${city}`);
}
const boundSay = say.bind(obj);
boundSay("seoul");
위 코드에서 boundSay
의 this
는 obj객체를 가리킨다.
결과
글을 봐도 기존에 this에 대해 공부해본 적이 없는 사람들은 한번에 이해하기 어려울 것이라 생각한다. 그만큼 난해한 키워드이다. 복잡한 코드에서는 더더욱 this라는 키워드는 활용하기 어려울 것이다. 많이 부딪혀보며 익히는 방법 밖에는 없다고 생각한다. 어렵지만 꼭 잘 알고 가야할 키워드이다. 글에서 this를 활용하는 법 모두를 담진 못했다. this를 더 깊게 이해하기 위해서는 실행 컨텍스트 객체에 대한 이해도 필요하다. 해당 내용은 추후에 기회가 되면 다뤄보려 한다.