요즘 앵귤러로 프로젝트를 구축하고 있는데 앵귤러는 객체지향 언어처럼 모듈 기능을 지원한다. 그러다보니 this 키워드를 많이 사용하는데, 이 참에 리액트로 개발할 때는 와닿지 않았었던 this의 정의와 사용법을 정리해보려한다.
자바스크립트에서 this는 함수가 실행 되고 있는 컨텍스트를 가리킨다.
this를 통해 함수의 컨텍스트와 환경을 조작할 수 있다.
this는 함수가 선언 되는 시점이 아닌 runtime 시 함수가 어떻게 호출되냐에 따라 동적으로 결정된다. 때문에 this가 사용될 수 있는 여러 케이스에서 this가 어떤 곳을 가리키는지 헷갈리는 경우가 많기 때문에 아래와 같이 정리해 보려한다.
this //[object Window]
웹 브라우저 콘솔에서 this를 호출하면 this를 호출한 곳의 웹브라우저의 전역환경인 window를 가르킨다.
따라서 전역환경에서 this 키워드를 사용하면 this 키워드가 사용된 전역 환경을 가르킨다.
const obj = {
a: function(){
console.log(this);
}
}
obj.a() // [object obj]
obj.b = function(){
console.log(this);
}
obj.b() // [object obj]
Math.getThis = function(){
console.log(this);
}
Math.getThis() // [object Math]
메소드는 객체 내부에 정의된 함수이다. 위와 같이 객체 내부에 메소드를 정의하고 정의한 객체 이름으로 해당 메소드를 this 키워드를 사용하면, this는 해당 메소드가 실행되는 환경이자 해당 메소드가 속한 객체를 가리킨다.
따라서, 메소드 내부에서 this를 사용하면 해당 메소드가 속한 객체에 접근할 수 있게 되어 객체의 프로퍼티와 메소드를 활용하여 객체의 동작을 정의하고 수행할 수 있다. 다음과 같이 this를 통해 메소드 체이닝을 할 수 있다.
const myObject = {
value: 0,
method1() {
this.value += 1;
return this;
},
method2() {
this.value *= 2;
return this;
},
method3() {
this.value -= 3;
return this;
}
};
const result = myObject.method1().method2().method3();
console.log(result.value); // -2
위처럼 method1,2,3에서 각각 myObject를 가리키는 this를 반환하고 있기 때문에 연속적으로 myObject 메소드를 사용하며 value 값을 변경할 수 있다.
그럼 다음과 같이 특정 변수에 메소드 함수를 할당하면 this의 값은 어떻게 바뀌게 될까?
const obj = {
a: function(){
console.log(this);
}
}
obj.a() // [object obj]
const a = obj.a
a() // [object window]
a 변수에서는 obj.a 함수의 참조를 가지게 된다. 때문에 a()를 호출할 때, a는 그저 함수의 참조일 뿐이므로 함수가 일반적인 방식으로 호출되게 된다. 따라서 a() 함수 내부의 this는 3번의 케이스와 동일하게 전역 환경을 가리키게 된다.
function callThis(){
console.log(this);
}
callThis(); //[object Window]
function callThisInCallback(cb){
cb();
}
callThisInCallback(callThis); //[object Window]
const obj = {
a: function(){
function f1(){
console.log(this);
};
f1();
}
}
obj.a() //[object Window]
일반 함수를 호출하면 해당 함수 내부의 this가 정의되지 않는다. this가 지정되지 않는 경우, this는 자동으로 전역 객체를 가리키게된다. 하지만 strict 모드에서는 암시적으로 this가 전역객체를 바운드하지 않기 때문에 undefined가 반환된다.
const person = {
name:'Saem',
age:11,
sayHi:function(){
function sayName(){
return this.name;
}
console.log(`Hi, my name is ${sayName()} and I'm ${this.age}.`);
}
}
person.sayHi(); // Hi, my name is and I'm 11.
3번의 상황과 같은 상황 때문에, 위의 예시와 같이 객체 메소드에 속한 함수를 실행했을 때 의도대로 this.name이 호출되지 않는 문제가 발생한다. 이 때문에 ES6에서 도입한 문법인 화살표 함수로 함수를 선언하여 this를 사용한다.
const person = {
name:'Saem',
age:11,
sayHi:function(){
const sayName = () => {
return this.name;
}
console.log(`Hi, my name is ${sayName()} and I'm ${this.age}.`)
}
}
person.sayHi(); //Hi, my name is Saem and I'm 11.
위의 예시처럼 화살표 함수를 사용하게 되면 this는 해당 함수의 상위 메소드의 this가 가리키는 [object person]을 가리키게 된다.
이유는 화살표 함수에는 this가 아예 없기 때문이다. 즉, function으로 선언한 함수를 실행할 땐 this가 존재하긴 하지만 값을 지정하지 않는데, 화살표 함수로 선언한 함수에는 this가 없다.
자바스크립트에서는 어떤 식별자(변수)를 찾을 때 현재 환경에서 그 변수가 없으면 바로 상위 환경을 검색한다. 그렇게 점점 상위 환경으로 타고 타고 올라가다가 변수를 찾거나 가장 상위 환경에 도달하면 그만두게 된다. 화살표 함수에서의 this 바인딩 방식도 이와 유사한 방식으로, 화살표 함수에는 this라는 변수 자체가 존재하지 않기 때문에 그 상위 환경에서의 this를 참조하게 된다.
class Person {
constructor(name, age){
this.name = name,
this.age = age
}
sayHi(){
console.log(`Hi, my name is ${this.name} and I'm ${this.age}`)
}
}
const user1 = new Person('Saem', '1')
const user2 = new Person('Jun', '6')
user1.sayHi() // Hi, my name is Saem and I'm 1
user2.sayHi() // Hi, my name is Jun and I'm 6
함수나 클래스를 new 키워드와 함께 생성자로 사용하면 this는 새로 생긴 객체를 가리키게 된다.
<input id="input" />
const inputEl = document.getElementById("input");
function callThisValue() {
console.log(this.value);
}
이벤트 핸들러 내에서 함수가 호출될 때에는 this는 이벤트를 호출한 DOM 요소로 정의된다.
const inputEl = document.getElementById("input");
inputEl.addEventListener("change", (event) => {
console.log(this);
console.log(this.value);
});
// [object window]
// undefined
화살표 함수는 위에서 언급했다시피 this가 존재하지 않기 때문에 this를 갖고 있는 렉시컬 환경의 this를 가리키기 때문에 window를 가리키게 된다.
function getThis(){
return this;
}
function callThis(){
console.log(this);
}
function getThisInCallback(cb){
return cb();
}
const obj = {
f1: getThis,
f2: getThisInCallback,
f3: getThisInCallback(getThis),
f4: getThisInCallback(callThis)
}
obj.f1() // [object obj]
obj.f2(getThis) // [object Window]
obj.f3 // [object Window]
obj.f4 // undefined
f1 속성은 getThis 함수가 obj 객체의 속성 값으로 할당되어 있으므로 obj.f1은 객체의 메소드로 동작하게 되어 getThis 메소드가 속한 obj를 반환한다.
f2 속성은 getThisInCallback 함수를 값으로 가지고 있다. obj.f2(getThis)를 호출하면 getThisInCallback 함수가 실행되면서 인자로 getThis 함수가 전달되고, getThis 함수 내부에서 cb()로 getThis함수를 호출한다. 이 때 getThis 함수는 일반적인 함수로 실행되기 때문에 전역 객체인 window를 반환한다.
f3 속성은 getThisCallback(getThis)의 결과를 값으로 가지고 있다. 이 때 getThis 함수는 getThisInCallback 함수의 인자로 전달되어 cb()으로 getThis 함수를 호출한다. 이 때 getThis 함수는 일반적인 함수로 실행되기 때문에 전역 객체인 window를 반환한다.
f4 속성은 getThisInCallback(callThis)의 결과를 값으로 가지고 있다. 이 코드는 getThisInCallback 함수를 호출하는 것이지만, 이때 callThis 함수를 인자로 전달한 결과가 f3에 저장된다. 이때 callThis 함수가 일반적인 방식으로 호출되므로 this는 전역 객체인 window를 가리킨다.
그러나 getThisInCallback 함수 내부에서 cb()로 함수를 호출하는데, f3에 저장된 값은 getThisInCallback(callThis) 호출 결과가 아니라 함수의 반환 값이기 때문에 cb()에서 실제로는 함수가 아닌 undefined가 호출되어 결과적으로 callThis 함수 내부의 this는 undefined가 된다. 따라서 obj.f3()을 호출하면 undefined를 반환한다.