모던 자바스크립트 Deep Dive 스터디
💡
this
는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다.
객체에서 메서드는 자신이 속한 객체의 상태, 즉 프로퍼티를 참조하고 변경하는 역할을 한다.
이때, 메서드가 자신이 속한 객체의 프로퍼티를 참조하려면 먼저 자신이 속한 객체를 가리키는 식별자가 필요할 것이다.
const circle = {
radius: 5,
getDiameter() {
return 2 * circle.radius;
}
};
console.log(circle.getDiameter()); // 10
객체 리터럴 방식으로 생성한 객체의 경우, 메서드 내부에서 메서드 사진이 속한 객체를 가리키는 식별자를 재귀적으로 참조할 수 있다.
getDiameter
메서드에서 circle
를 참조하는 표현식이 평가되는 것은 getDiameter
메서드가 호출되고 함수 몸체가 실행되는 시점이다.
위 객체 리터럴은 circle
변수에 할당되기 직전에 평가된다. 따라서 getDiameter
메서드가 호출되는 시점에는 이미 객체 리터럴의 평가가 완료되어 circle
에 할당되어 있다. 그러므로 getDiameter
메서드는 circle
식별자를 참조할 수 있다.
하지만, 이렇게 재귀적 참조 방식은 일반적이지 않으며 바람직하지 않다.
또한, 객체 리터럴이 아닌 생성자 함수 방식이라면 어떨까?
function Circle(radius) {
???.radius = radius;
};
Circle.prototype.getDiameter = function() {
return 2 * ???.radius;
}
const circle = new Circle(5);
생성자 함수 내부에서 프로퍼티 또는 메서드를 추가하기 위해서 자신이 생성할 인스턴스를 참조할 수 있어야 한다.
하지만 인스턴스를 만들기 위해서는 생성자 함수가 먼저 존재할 필요가 있다.
따라서, 생성자 함수가 프로퍼티, 메서드를 만들기 위해서는 존재하기 이전인 인스턴스를 미리 참조할 수 있는 특수한 식별자가 필요하다.
this
는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수다.
단, this
가 가리키는 값, 즉 this
바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
// 전역에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window
// 일반 함수 내부에서 this는 전역 객체 window를 가리킨다.
function square(number) {
console.log(this); // window
return number*number;
}
//메서드 내부에서 this는 메서드를 호출한 객체를 가리킨다.
const person = {
name: "sangjo",
getName() {
console.log(this); // {name: "sangjo", getName: ƒ}
return this.name;
}
};
console.log(person.getName()); // sangjo
// 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
function Person(name) {
this.name = name;
console.log(this); // Person {name: "sangjo"}
};
const me = new Person("sangjo")
💡
this
바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
함수를 호출하는 방식은 다양하다.
Function.prototype.apply/call/bind
메서드에 의한 간접 호출const foo = function() {
console.dir(this);
};
// 1. 일반 함수 호출
// 일반 함수 내부의 this는 전역 객체를 가리킨다.
foo(); // window
// 2. 메서드 호출
// 메서드를 호출한 객체를 가리킨다.
const obj = { foo };
obj.foo(); // obj
// 3. 생성자 함수 호출
// 생성자 함수가 생성한 인스턴스를 가리킨다.
new foo(); // foo {}
// 4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
// 메서드의 인수에 따라 결정된다.
const bar = { name: "bar" };
foo.apply(bar); // bar
foo.call(bar); // bar
foo.bind(bar)(); // bar
💡 일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부의
this
에는 전역 객체가 바인딩된다.
전역 함수는 물론이고 중첩 함수를 일반 함수로 호출하면 함수 내부의 this
에는 전역 객체가 바인딩된다.
메서드 내에서 정의한 중첩 함수 역시 일반 함수라면 this
에는 전역 객체가 바인딩된다.
하지만 this
는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 객체를 생성하지 않는 일반 함수에서 this
는 의미가 없다.
그리고 strict mode
가 적용된 일반 함수 내부의 this
에는 undefined
가 바인딩된다.
콜백 함수가 일반 함수로 호출된다면 콜백 함수 내부의 this에도 전역 객체가 바인딩 된다.
이 경우 곤란한 상황이 발생할 수 있다.
var value = 1;
const obj = {
value: 100,
foo() {
console.log("foo's this", this); // {value: 100, foo: ƒ}
setTimeout(function() {
console.log("callback's this", this); // window
console.log("callback's this.value", this.value); // 1
}, 1000)
}
}
중첩 함수 또는 콜백 함수는 외부 함수의 일부 로직을 대신하는 경우가 대부분이다.
하지만 외부 함수인 메서드와 중첩 함수 또는 콜백 함수의 this가 일치하지 않는다는 것은 이들을 헬퍼 함수로 동작하기 어렵게 만든다.
이 경우 사용할 수 있는 방법은 다음과 같다.
var value = 1;
const obj = {
value: 100,
foo() {
// 메서드의 this를 that에 할당한다.
const that = this;
setTimeout(function() {
// this대신 that을 참조한다.
console.log(that.value); // 100
}, 1000)
}
}
var value = 1;
const obj = {
value: 100,
foo() {
// 콜백 함수에 명시적으로 this를 바인딩한다.
setTimeout(function() {
console.log(this.value); // 100
}.bind(this), 1000)
}
}
var value = 1;
const obj = {
value: 100,
foo() {
// 화살표 함수 내부의 this는 언제나 상위 스코프의 this를 가리킨다.
setTimeout(() => console.log(this.value), 1000) // 100
}
}
💡 메서드 내부의
this
는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
}
const me = new Person("sangjo");
console.log(me.getName()); // 1. sangjo
Person.prototype.name = "LEE"
console.log(Person.prototype.getName()); // 2. LEE
1의 경우, getName
메서드를 호출한 객체는 me
이므로 this
는 me
를 가리킨다.
2의 경우, getName
메서드를 호출한 객체는 Person.prototype
이므로 this
는 Person.prototype
을 가리킨다.
💡 생성자 함수 내부의 this에는 미래에 생성할 인스턴스가 바인딩된다.
💡 Function.prototype.apply/call/bind 메서드는 this로 사용할 객체를 전달받는다.
const foo = function() {
console.dir(this);
};
const bar = { name: "bar" };
foo(); // window
foo.apply(bar); // bar
foo.call(bar); // bar
foo.bind(bar)(); // bar