목표
22. this
22-1. this 키워드
- this 키워드를 알아보기 전 객체에 대해 짚고 넘어가야 한다.
- 객체는 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조이다.
- 이 때, 메서드는 자신이 속한 객체의 프로퍼티를 참조하고 변경할 수 있어야 한다. 그러기 위해서는 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 한다.
- 자바스크립트는 이를 위해 this라는 특수한 식별자를 제공한다.
- this는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수(self-referencing variable)이다.
- 이 this 키워드를 사용하면 객체의 메서드와 인스턴스에 접근할 수 있다.
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.getDiameter = function () {
return 2 * this.radius;
};
const cir = new Circle(5);
- 위의 코드에서 this는 메서드를 호출한 객체, 즉 cir을 가리킨다.
22-1-1. this는 누가 만들까?
- this는 자바스크립트 엔진에 의해 암묵적으로 생성되는데 생성자 함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다.
22-2. this 바인딩
- 바인딩이란 식별자와 값을 연결하는 과정을 말한다.
- 변수 선언은 변수 이름(식별자)와 확보된 메모리 공간의 주소를 바인딩하는 것이라면 this 바인딩은 this와 this가 가리킬 객체를 연결하는 것이다.
- this 바인딩은 함수 호출방식에 의해 동적으로 결정된다.
- 예를 들어, 생성자 함수와 객체 리터럴을 통해 객체를 생성해 보면
function Circle(radius) {
this.radius = radius;
}
const cir = new Circle(5);
const circle = {
radius = 5,
getDiameter() {
return 2 * this.radius;
}
};
- 생성자 함수의 this는 인스턴스인 cir을 가리키고 객체 리터럴의 this는 circle을 가리킨다.
22-3. 함수 호출 방식에 따른 this 바인딩
- 동일한 함수도 다양한 방식으로 호출할 수 있다.
- 일반 함수 호출
- 메서드 호출
- 생성자 함수 호출
- Function.prototype.apply/call/bind 메서드에 의한 간접 호출
22-3-1. 일반 함수 호출
- 기본적으로 this에는 전역 객체(global object)가 바인딩된다.
function foo() {
console.log(`foo's this: ${this}`);
function bar() {
console.log(`bar's this: ${this}`);
}
bar();
}
foo();
"foo's this: [object Window]"
"bar's this: [object Window]"
- 객체를 생성하지 않는 일반 함수 내부의 this에는 전역 객체가 바인딩된다.
- 참고로 strict mode가 적용되면 this에는 undefined가 바인딩된다.
var value = 1;
const obj = {
value: 100,
foo() {
console.log(`foo's this: ${this}`);
setTimeout(function () {
console.log(`callback's this: ${this}`);
console.log(this.value);
}, 1000);
},
};
obj.foo();
- 또한, 콜백함수가 일반 함수로 호출된다면 콜백 함수 내부의 this에도 전역 객체가 바인딩된다.
- 위의 코드에서 setTimeout 함수의 콜백함수의 this는 window를 가리키고 this.value는 전역 변수를 가리킨다.
setTimeout
- setTimeout 함수는 두 번째 인수로 전달한 시간(ms)만큼 반복하여 첫 번째 인수로 전달한 콜백 함수를 호출하는 타이머 함수이다.
- 하지만, 콜백 함수 내부의 this에 전역 객체가 바인딩되는 것은 문제가 있다.
- 위 코드에서 콜백 함수의 this.value는 외부 함수인 foo의 value가 아닌 전역 객체의 value를 참조하고 있으므로 콜백 함수를 외부 함수의 헬퍼 함수로 동작하기 어렵게 만든다.
- 이는 Function.prototype.bind 메서드나 화살표 함수를 사용하여 문제를 해결할 수 있다.
var value = 1;
const obj = {
value: 100,
foo() {
setTimeout(function () {
console.log(this.value);
}.bind(this), 1000);
},
};
obj.foo();
var value = 1;
const obj = {
value: 100,
foo() {
setTimeout(() => console.log(this.value), 1000);
},
};
obj.foo();
22-3-2. 메서드 호출
- 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
const person = {
name: "Lee",
getName() {
return this.name;
},
};
console.log(person.getName());
"Lee"
- 메서드 getName은 프로퍼티에 바인딩된 함수이다.
- 따라서, getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함된 것이 아닌 독립적으로 존재하는 별도의 함수 객체를 가리키게 된다.
- 이를 응용하면 getName 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.
const person = {
name: "Lee",
getName() {
return this.name;
},
};
const otherPerson = {
name: "Jung",
};
otherPerson.getName = person.getName;
console.log(otherPerson.getName());
const getName = person.getName;
console.log(getName());
22-3-3. 생성자 함수 호출
- 생성자 함수 내부의 this에는 생성자 함수가 생성할 인스턴스가 바인딩된다.
function Circle(radius) {
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
const circle1 = new Circle(5);
const circle2 = new Circle(10);
console.log(circle1.getDiameter());
console.log(circle2.getDiameter());
10
20
22-3-4. 간접 호출
- apply, call, bind 메서드는 Function.prototype의 메서드이다.
function getThis() {
return this;
}
const arg = { a: 1 };
console.log(getThis());
console.log(getThis.apply(arg));
console.log(getThis.call(arg));
- apply와 call 메서드의 본질적인 기능은 함수를 호출하는 것이다.
- 함수를 호출하면서 첫 번째 인수로 전달한 특정 객체를 호출함 함수의 this에 바인딩한다.
- 인수를 전달하는 방식에 따라 apply와 call을 구분하여 사용하는데
function getThis() {
console.log(arguments);
return this;
}
const arg = { a: 1 };
console.log(getThis.apply(arg, [1, 2, 3]));
console.log(getThis.call(arg, 1, 2, 3));
- apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달한다.
- call 메서드는 호출할 함수의 인수를 쉼표로 구분하여 전달한다.
- apply와 call 메서드의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하는 경우이다.
- arguments 객체는 배열이 아니기 때문에 slice와 같은 배열 메서드를 사용할 수 없지만 apply와 call 메서드를 이용하면 가능하다.
function getThis() {
const arr = Array.prototype.slice.apply(arguments);
console.log(arr);
return arr;
}
getThis([1, 2, 3]);
- bind 메서드는 apply와 call 메서드와는 달리 함수를 호출하지 않고 this로 사용할 객체만 전달한다.
- bind 메서드는 this와 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 유용하게 사용된다.
const person = {
name: 'Lee',
getName(callback) {
setTimeout(callback.bind(this), 100);
}
};
person.getName(function() {
console.log(`My name is ${this.name}`);
});
"My name is Lee"
22-3-5. 정리
- 함수 호출 방식에 따라 this 바인딩이 동적으로 결정되는 것을 정리해보면 다음과 같다.