this 키워드
- 객체는 프로퍼티와 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조
- 메서드는 자신이 속한 객체의 상태 (프로퍼티)를 참조하고 변경할 수 있어야 함 ⇒ 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 자신이 속한 객체를 가리키는 식별자를 참조할 수 있어야 함
- 객체 리터럴 방식으로 생성한 객체의 경우, 메서드 내부에서 자신이 속한 객체를 가리키는 식별자를 재귀적으로 참조 가능
- 객체 리터럴은 circle 변수에 할당되기 직전에 평가되므로, getDiameter 메서드가 호출되는 시점에는 이미 객체 리터럴의 평가가 완료되어 객체가 생성됨 → circle 식별자에 생성된 객체가 할당된 이후이기 때문에 메서드 내부에서 circle 식별자 참조 가능
- 자신이 속한 객체를 재귀적으로 참조하는 방식은 일반적이지 X, 바람직하지 X ⇒ 생성자 함수 방식으로 인스턴스 생성
- 생성자 함수로 인스턴스 생성하려면 생성자 함수가 존재해야 함 but 생성자 함수 정의 시점에는 아직 인스턴스 생성하기 이전이므로 생성자 함수가 생성할 인스턴스를 가리키는 식별자를 알 수 X ⇒ 자바스크립트에서 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 특수한 식별자 this제공
- this : 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수
- this를 통해 자신이 속한 객체나 자신이 생성할 인스턴스의 프로퍼티 및 메서드 참조 가능
- this는 자바스크립트 엔진에 의해 암묵적으로 생성되며, 코드 어디서든 참조 가능
- 함수 호출 시 this가 암묵적으로 함수 내부에 전달되어 지역 변수처럼 사용 가능
- 단, this가 가리키는 값 (this 바인딩)은 함수 호출 방식에 의해 동적으로 결정
- this 바인딩 : this와 this가 가리킬 객체를 바인딩하는 것 (바인딩 : 식별자와 값을 연결하는 과정)
- 객체 리터럴과 this 사용
// 객체 리터럴
const circle = {
radius: 5,
getDiameter() {
// this는 메서드 호출한 객체를 가리킴
return 2 * this.radius;
}
}
console.log(circle.getDiameter()); // 10
// 생성자 함수
function Circle(radius) {
// this는 생성자 함수가 생성할 인스턴스를 가리킴
this.radius = radius
}
Circle.prototype.getDiameter = function() {
// this는 생성자 함수가 생성할 인스턴스를 가리킴
return 2 * this.radius;
}
// 인스턴스 생성
const circle = new Circle(5);
console.log(circle.getDiameter()); // 10
- 자바스크립트의 this는 함수가 호출되는 방식에 따라 this에 바인딩될 값, 즉 this 바인딩이 동적으로 결정
- 전역에서 this 사용 시 전역 객체 window를 가리킴
- 일반 함수 내부에서 this 사용 시 전역 객체 window를 가리킴
- 메서드 내부에서 this 사용 시 메서드를 호출한 객체를 가리킴
- 생성자 함수에서 this 사용 시 생성자 함수가 생성할 인스턴스 가리킴
- strict mode가 적용된 일반 함수 내부에서 this 사용 시 undefined가 바인딩 됨
- this는 자기 참조 변수여서 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있기 때문
함수 호출 방식과 this 바인딩
일반 함수 호출
- 기본적으로 this에 전역 객체가 바인딩 됨
- this는 자기 참조 변수이므로 객체를 생성하지 않는 일반 함수에서는 의미 X ⇒ strict mode 적용된 일반 함수 내부의 this는 undefined가 바인딩 됨
- 어떠한 함수라도 일반 함수로 호출되면 중첩 함수 내의 this에도 전역 객체가 바인딩 됨 (ex. 메서드 내의 중첩 함수, 콜백 함수)
- 중첩 함수와 콜백 함수는 외부 함수를 돕는 헬퍼 함수 역할을 하는데, 외부 함수인 메서드와 가리키는 this가 일치하지 않으면 헬퍼 함수로 동작하기 어려움
- 메서드 내부의 중첩 함수나 콜백 함수의 this 바인딩을 메서드의 this 바인딩과 일치시키는 방법
- this 바인딩을 다른 변수에 할당 후 헬퍼 함수 내부에서 this 대신 그 변수를 참조하는 것
- var value = 1; const obj = { value: 100, foo() { // this 바인딩(obj)을 변수 that에 할당 const that = this; // 콜백 함수 내부에서 this 대신 that 참조 setTimeout(function() { console.log(that.value); }, 100); } } obj.foo();
- Function.prototype.apply/call/bind 메서드 사용하여 this 바인딩 일치
- 화살표 함수 사용하여 this 바인딩 일치
- var value = 1; const obj = { value: 100, foo() { // 화살표 함수 내부의 this는 상위 스코프의 this를 가리킴 setTimeout(() => console.log(this.value), 100); } } obj.foo();
메서드 호출
- 메서드 호출 시 메서드 이름 앞에 마침표(.) 연산자 앞에 기술한 객체가 바인딩 됨
- 메서드 내부의 this는 메서드를 소유하고 있는 객체가 아닌 메서드를 호출한 객체에 바인딩 됨
const person = {
name: 'Lee',
getName() {
// this는 메서드를 호출한 객체에 바인딩 됨
return this.name;
}
}
console.log(person.getName()); // Lee
- 메서드를 다른 변수에 할당하여 일반 함수로 호출 시 this는 전역 객체 가리킴
const anotherPerson = {
name: 'Kim'
}
// 위 person 객체의 getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;
console.log(anotherPerson.getName()); // Kim
// 위 person 객체의 getName 메서드를 변수에 할당
const getName = person.getName;
// 일반 함수로 호출된 getName 함수 내부의 this.name은 window.name과 같음
console.log(getName()); // ''
생성자 함수 호출
- 생성자 함수가 생성할 인스턴스에 바인딩 됨 (new 연산자 사용하여 생성자 함수로 호출해야 됨)
// 생성자 함수
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()); // 10
console.log(circle2.getDiameter()); // 20
// new 연산자 없이 호출하면 일반 함수로 호출됨
const circle3 = Circle(15);
console.log(circle3); // undefined
Function.prototype.apply/call/bind 메서드에 의한 간접 호출
- apply, call, bind 메서드는 Function.prototype의 메서드 (모든 함수가 상속받아 사용 가능)
- Function.prototype.apply와 Function.prototype.call 메서드는 this로 사용할 객체와 인수 리스트를 인수로 전달받아 함수 호출
- apply와 call의 본질적인 기능 : 함수 호출
- apply는 두 번째 인수에 인수 리스트의 배열 또는 유사 배열 객체를 전달하고, call은 두 번째 인수에 인수 리스트를 전달함
function getThisBinding() {
return this;
}
const thisArg = {a:1};
// apply는 인수 리스트의 배열 또는 유사 배열 객체 전달
console.log(getThisBinding.apply(thisArg, [1, 2, 3])); // {a:1}
// call은 인수 리스트를 쉼표로 구분하여 전달
console.log(getThisBinding.call(thisArg, 1, 2, 3)); // {a:1}
- apply와 call의 대표적인 용도 : arguments 객체와 같은 유사 배열 객체에 배열 메서드 사용하는 용도
- Function.prototype.bind는 함수 호출 X, 첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환
- bind 메서드는 메서드의 this와 헬퍼 함수 (메서드 내부의 중첩 함수, 콜백 함수)의 this가 불일치하는 문제 해결
// apply, call
function convertArgsToArray() {
console.log(arguments);
// Array.prototype.slice를 인수 없이 호출하면 배열의 복사본 생성
const arr = Array.prototype.slice.call(arguments);
// const arr = Array.prototype.slice.apply(arguments);
console.log(arr);
return arr;
}
convertArgsToArray(1, 2, 3); // [1, 2, 3]
// bind
const person = {
name: 'Lee',
foo(callback) {
// bind 메서드로 콜백 함수 내부의 this 바인딩을 전달
setTimeout(callback.bind(this), 100);
}
}
person.foo(function() {
console.log(`Hi! my name is ${this.name}.`); // Hi! my name is Lee.
)}