❓this란
this는 자기 참조 변수를 의미하는 식별자이고, this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키며 해당 프로퍼티 또는 메서드를 참조하는 것이 가능하다. 간단한 예제를 먼저 보자.
console.log(this) // window
브라우저 환경에서 위의 코드를 입력하면 this는 전역 객체인 window를 참조한다. 그렇다면 this는 전역객체만을 가리키냐? 그건 아니다.
const user = {
name: "kim",
getName() {
return this.name;
}
}
console.log(user.getName()) // kim
객체 리터럴에서는 this가 자신이 속한 객체를 바인딩 하는 것을 알 수 있다.
function User(name) {
this.name = name;
}
User.prototype.getName = function() {
return this.name
}
const user1 = new User("Kim");
const user2 = new User("Lee");
console.log(user1.getName()) // Kim
console.log(user2.getName()) // Lee
생성자 함수의 this는 생성자 함수가 생성할 인스턴스를 가리키고, 각각 생성된 인스턴스를 가리키는 것을 알 수 있다. 이렇게 this는 상황에 따라 가리키는 대상이 달라진다. 특히 자바스크립트의 this는 바인딩이 동적으로 결정되는데 다시 말하면, this에 바인딩되는 값은 함수의 호출 방식에 따라 달라진다.
함수 호출 방식에 따른 this 바인딩
함수의 상위 스코프 결정은 함수 정의가 평가되어 함수 객체가 생성되는 시점에 이루어지지만, this 바인딩은 함수 호출 시점에 결정된다.
함수의 호출 방식은 다음과 같이 분류할 수 있다.
1️⃣ 일반 함수 호출
2️⃣ 메서드 호출
3️⃣ 생성자 함수 호출
4️⃣ Function.prototype 메서드인 apply, call, bind에 의한 간접 호출
1️⃣ 일반 함수 호출
function foo() {
console.log(this) // window
function boo() {
console.log(this) // window
function zoo() {
console.log(this) // window
}
zoo();
}
boo();
}
foo();
기본적으로 일반 함수를 호출하는 경우 this에는 전역 객체가 바인딩 된다. 일반적으로 this는 객체의 프로퍼티 또는 메서드를 참조하기 위해 사용하기 때문에 위와 같이 일반 함수에서 this를 사용하는 것은 무의미하다.
메서드 내의 중첩 함수 또는 콜백 함수도 일반 함수로 전달된다면 this에는 전역 객체가 바인딩 된다. 대부분의 중첩 함수 또는 콜백 함수는 해당 함수를 가지는 외부 함수를 돕는 역할을 하기때문에 전역 객체가 아닌 해당 메서드의 this 바인딩과 일치시키기 위해서는 Function.prototype의 apply, call, bind 메서드로 명시적으로 this를 바인딩 하거나, 화살표 함수를 사용한다.
2️⃣ 메서드 호출
메서드 내부의 this에는 메서드를 호출한 객체가 바인딩 된다. 다음 예제를 살펴보자.
const user = {
name: "Kim",
getName() {
return this.name
}
}
console.log(user.getName()) // Kim
getName 메서드는 user라는 객체의 메서드로 정의되어있기 때문에 메서드는 user 객체의 프로퍼티로 존재하는 함수다. 함수 객체는 독립적으로 존재하는 별도의 객체이기 때문에 함수 객체 자체가 user 객체에 포함된 것은 아니다. 그림으로 보면 다음과 같다.
따라서 메서드 내부의 this는 객체가 해당 메서드를 프로퍼티로 갖고있다고 해서 해당 객체에만 this가 바인딩되는 것이 아니고 메서드를 호출한 객체에 바인딩 된다.
const user = {
name: "Kim",
getName() {
return this.name
}
}
const user2 = {
name: "Lee"
}
user2.getName = user.getName
console.log(user2.getName()) // Lee
위의 코드는 다음과 같은 형태를 띌 것이다.
3️⃣ 생성자 함수 호출
생성자 함수 내부의 this는 생성자 함수를 통해 생성할 인스턴스에 바인딩된다. 하지만 생성자 함수 또한 함수이기 때문에 new 연산자 없이 함수를 호출하면 일반 함수처럼 동작하기 때문에 주의해야 한다.
function User(name) {
this.name = name;
this.greeting = function () {
return `Hi ! ${this.name}`;
};
}
const user1 = new User("kim");
const user2 = new User("lee");
console.log(user1.greeting()); // Hi ! kim
console.log(user2.greeting()); // Hi ! lee
4️⃣ Function.prototype의 메서드인 apply, call, bind에 의한 간접 호출
apply와 call은 함수를 호출한다. 첫번째 인자로는 this로 사용할 객체를 전달하고 두번째 인자로는 apply 메서드는 함수에게 전달할 인수 리스트를 배열로 전달하고 call 메서드는 쉼표로 구분하여 전달한다.
function getThisBinding() {
console.log(arguments)
return this
}
const obj = {
value: 1
}
console.log(getThisBinding.apply(obj, [1,2,3])
// Argument(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// {value: 1}
console.log(getThisBinding.call(obj, 1, 2, 3))
// Argument(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f]
// {value: 1}
apply와 call은 호출할 함수에 인수를 전달하는 방식이 다를 뿐 동작은 동일하다.
function getThisBinding() {
return this
}
const obj = {
value: 1
}
console.log(getThisBinding.bind(obj)) // getThisBinding
console.log(getThisBinding.bind(obj)()) // {value: 1}
bind 메서드는 명시적으로 호출을 통해 함수를 호출해야한다.