this
파헤치기다른 언어를 사용하다가 JavaScript를 처음 쓰는 사람들이 자주 헷갈려하는 개념중 하나가 바로 this
이다.
다른 언어와는 달리 동작하기 때문인데, 일반적으로 this
는 class
에서만 사용하며, class
로 생성한 인스턴스 객체를 의미하나, JavaScript에서는 그렇지 않고 this
가 가르키는 대상이 항상 달라진다.
this
를 먼저 간략하게 설명하자면, this
는 자신이 속한 객체 또는 자신이 생성할 instance를 가리키는 자기 참조 변수(self-reference variable)이다.
또, this
는 실행컨텍스트가 생성될 때 결정된다. 실행컨텍스트는 함수를 호출할 때 생성되므로, this는 함수를 호출할 때 결정된다.
this
의 동작 방식우선 콘솔창에 this
를 입력해보자.
this; // Window {}
전역 객체인 Window
를 가리킨다.
(client(브라우저)에서는 Window
Node.js에서는 global
을 가리킴)
그렇다면 함수 안에서는?
function what() { console.log(this); };
what(); // Window {}
그렇다. 함수 안에서도 Window
를 가리킨다.
이번엔 객체의 경우를 살펴보자.
let obj = {
what: function() { console.log(this); },
};
obj.what(); // {what: ƒ}
let oh = obj.what;
oh(); // Window {}
메소드로 호출했을때, obj
객체를 가리킨다.
하지만 함수(oh
)는 단순히 obj.what
을 꺼내온 것이기 때문에 메소드라 할 수 없다. 객체가 함수를 호출해야 메소드라고 할 수 있다.
그냥 함수인 oh는 전역객체, Window
를 가리킨다.
let obj = { name: 'kim' };
function what() {
console.log(this);
}
what(); // Window
what.bind(obj).call(); // obj
what.call(obj); // obj
what.apply(obj); // obj
명시적으로 this
를 바꾸는 함수 메소드인 bind
, call
, apply
를 사용하면 this
가 객체를 가리킨다.
이에 대해선 밑에 더 자세하게 후술하겠다.
이제 생성자 함수를 살펴보자.
function User(name,isAdmin) {
this.name = name;
this.isAdmin = isAdmin;
}
let newUser = User("Mike",false);
console.log(window.name); // Mike
여기서 new
를 사용하지 않고 생성하면 this
는 또 전역 객체를 가리키게 된다.
하지만 new
를 사용한다면?
let user = new User("Mike",false);
console.log(user.name); // Mike
console.log(user.isAdmin); // false
this
가 정상적으로 새롭게 생성된 instance를 가리키게 된다.
document.body.addEventListener('click',function(){
console.log(this); // <body>
});
하지만 addEventListener
같은 경우는, this
가 Window
가 아니라 <body>를 가리킨다.
바로 클릭으로 인해 이벤트가 발생할 때, 내부적으로 this가 바뀐 것이다. 내부적으로 바뀐 것이기 때문에 동작을 외울 수밖에 없다고 한다.
이런 경우엔 어떻게 해결해야할까?
위와 같은 문제는 ES6 화살표 함수 (arrow function)을 사용하면 해결할 수 있다.
document.body.addEventListener('click',() => {
console.log(this); // Window {}
});
화살표 함수를 사용하게 되면, this
의 대상이 어떤 객체가 호출했느냐로 결정되지 않는다.
함수 내부에 this
는 없으며, scope chain의 가장 가까운 this
로 대상을 결정한다.
다른 예시를 한번 살펴보자.
function User(name,isAdmin){
this.name = name;
this.isAdmin = isAdmin;
document.body.addEventListener('click',function() {
console.log(this) // <body>
console.log(this.name); // undefined
console.log(this.isAdmin); //undefined
});
}
const kendrick = User('Kendrick', true);
일반 함수를 사용했을 때, this
는 <body>를 가리킨다.
화살표 함수를 사용해보겠다.
function User(name,isAdmin){
this.name = name;
this.isAdmin = isAdmin;
document.body.addEventListener('click',() => {
console.log(this); // Window {}
console.log(this.name); // Kendrick
console.log(this.isAdmin); // true
});
}
const a = User('Kendrick', true);
보이는 바와 같이 scope chain의 가장 가까운 this
로 대상을 결정했다.
this
binding (명시적 binding)📄 바인딩 (binding) 이란?
Javascript에서 this
가 참조하는 것은 함수가 호출되는 방식에 따라 결정되는데, 이것을 "this binding"이라고 한다.
위에 작성한 화살표함수도 this
를 새롭게 binding했다고 할 수 있다.
📄 call, apply
const obj = { name : "Tyson"};
const profile = function(age){
console.log(`Hi, my name is ${this.name} and I'm ${age} years old.`);
}
profile("54"); // Hi, my name is and I'm 54 years old.
profile.call(obj,24); // Hi, my name is Tyson and I'm 24 years old.
profile.apply(obj,[24]); // Hi, my name is Tyson and I'm 24 years old.
call
과 apply
는 함수를 호출하는 함수이다. 그러나 그냥 실행하는 것이 아니라 첫 번째 인자에 this
로 setting하고 싶은 객체를 넘겨주어 this
를 바꾸고나서 실행한다.
profile("54")
의 this
는 전역객체 Window
를 가리키고,
두 번째 실행인 profile.call(obj, 24)
와 세 번째 실행인 say.apply(obj, "seoul")
은 this
를 obj로 대체한다.
그러자 정상적으로 대체된 값이 출력됐다.
call
과 apply
의 차이점은 단 하나이다.
.call(this, parameter1, parameter2 ...)
.apply(this, [parameter1], [parameter2] ...);
이렇게 apply
는 두번째 인자부터는 배열에 담아야 한다는 점이다.
📄 bind
bind
함수는 함수가 가리키는 this
만 바꾸고 실행하지는 않는다.
더 자세히 말하자면 this
를 정의하고 나서 그 함수를 복사해 새로운 함수를 만들어 return하는 것이다.
const obj = { name : "Tyson"};
const profile = function(age){
console.log(`Hi, my name is ${this.name} and I'm ${age} years old.`);
}
const profile2 = profile.bind(obj);
profile2(24); // Hi, my name is Tyson and I'm 24 years old.
profile.call(obj,24); // Hi, my name is Tyson and I'm 24 years old. (결괏값이 위와 같다)
이를 보면, .bind(obj)(24)
와 .call(obj,24)
는 같다는 것을 알 수 있다.
this🤞