자신이 속한 객체의 프로퍼티를 참조해야한다.
this
키워드가 메서드 자신이 속한 객체를 가리키고 있기에 가능하다.
const circle = {
radius: 5, // 프로퍼티: 객체 고유의 상태 데이터
getDiameter() {
// 메서드: 프로퍼티를 참조하고 조작하는 동작
// 자신이 속한 객체인 circle을 참조 할 수 있어야 한다.
return 2 * circle.radius;
},
};
console.log(circle.getDiameter()); // 10
circle
내부에서 circle
을 참조하는 형태다.getDiameter()
메서드가 실행되기 전에 circle 객체가 생성되어 있기 때문이다.function Circle(radius) {
// 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다.
????.radius = radius;
}
// 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다.
Circle.prototype.getDiameter = function () {
return 2 * ????.radius;
}
// 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수를 정의해야 한다.
const circle = new Circle(5);
📄 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는 전역 객체 window를 가리킨다.
console.log(this); // window
function square(number) {
// 일반 함수 내부에서 this는 전역 객체 window를 가리킨다.
console.log(this); // window
return number * number;
}
square(2);
const person = {
name: 'Lee',
getName() {
// 메서드 내부에서 this는 메서드를 호출한 객체를 가리킨다.
console.log(this); // {name: "Lee", getName: ƒ}
return this.name;
}
};
console.log(person.getName()); // Lee
function Person(name) {
this.name = name;
// 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
console.log(this); // Person {name: "Lee"}
}
const me = new Person('Lee');
this
는 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있다.(function(){
'use strict';
function print(){
console.log(this);
}
print(); // undefined
}())
// 일반 함수 내부의 this는 window가 바인딩되고, strict mode에서는 undefined가 바인딩된다.
(function(){
'use strict';
function printThis(){
console.log(this);
}
const obj = {};
obj.print = printThis;
obj.print(); // obj: {print: ƒ}
}())
//객체의 프로퍼티로 함수를 할당하면 메서드가 되어 객체가 this에는 객체가 바인딩 된다.
this
바인딩은 함수 호출 방식에 따라 동적으로 결정된다.
❗ 렉시컬 스코프와 this 바인딩은 결정 시기가 다르다.
렉시컬 스코프는 함수 정의가 평가되어 함수 객체가 생성되는 시점에 결정됨 / this
는 함수가 호출시 결정된다.
함수를 호출하는 다양한 방식
// this 바인딩은 함수 호출 방식에 따라 동적으로 결정된다.
const foo = function () {
console.dir(this);
};
// 동일한 함수도 다양한 방식으로 호출할 수 있다.
// 1. 일반 함수 호출
// foo 함수를 일반적인 방식으로 호출
// foo 함수 내부의 this는 전역 객체 window를 가리킨다.
foo(); // window
// 2. 메서드 호출
// foo 함수를 프로퍼티 값으로 할당하여 호출
// foo 함수 내부의 this는 메서드를 호출한 객체 obj를 가리킨다.
const obj = { foo };
obj.foo(); // obj
// 3. 생성자 함수 호출
// foo 함수를 new 연산자와 함께 생성자 함수로 호출
// foo 함수 내부의 this는 생성자 함수가 생성한 인스턴스를 가리킨다.
new foo(); // foo {}
// 4. Function.prototype.apply/call/bind 메서드에 의한 간접 호출
// foo 함수 내부의 this는 인수에 의해 결정된다.
const bar = { name: 'bar' };
foo.call(bar); // bar
foo.apply(bar); // bar
foo.bind(bar)(); // bar
일반 함수로 호출 하면, 기본적으로 this에는 전역 객체가 바인딩된다.
// 일반 함수로 호출하는 경우 기본적으로 this에는 전역 객체가 바인딩된다.
function foo() {
console.log("foo's this: ", this); // window
function bar() {
console.log("bar's this: ", this); // window
}
bar();
}
foo();
});
// strict mode에서는 this에 undefined가 바인딩된다.
function foo() {
'use strict';
console.log("foo's this: ", this); // undefined
function bar() {
console.log("bar's this: ", this); // undefined
}
bar();
}
foo();
// var 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티다.
var value = 1;
// const 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티가 아니다.
// const value = 1;
const obj = {
value: 100,
foo() {
console.log("foo's this: ", this); // {value: 100, foo: ƒ}
console.log("foo's this.value: ", this.value); // 100
// 메서드 내에서 정의한 중첩 함수
function bar() {
console.log("bar's this: ", this); // window
console.log("bar's this.value: ", this.value); // 1
}
// 메서드 내에서 정의한 중첩 함수도 일반 함수로 호출되면 중첩 함수 내부의 this에는 전역 객체가 바인딩된다.
bar();
}
};
obj.foo();
// that 사용
var value = 1;
const obj = {
value: 100,
foo() {
// this 바인딩(obj)을 변수 that에 할당한다.
const that = this;
// 콜백 함수 내부에서 this 대신 that을 참조한다.
setTimeout(function () {
console.log(that.value); // 100
}, 100);
}
};
obj.foo();
// Function.prototype.apply/call/bind메서드 활용
var value = 1;
const obj = {
value: 100,
foo() {
// 콜백 함수에 명시적으로 this를 바인딩한다.
setTimeout(function () {
console.log(this.value); // 100
}.bind(this), 100);
}
};
obj.foo();
// 화살표 함수 활용
var value = 1;
const obj = {
value: 100,
foo() {
// 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.
setTimeout(() => console.log(this.value), 100); // 100
}
};
obj.foo();
메서드 내부의
this
에는 메서드를 호출한 객체, 즉 메서드를 호출할 때 메서드 이름 앞의 마침표. 연산자 앞에 기술한 객체가 바인딩된다.
const person = {
name: 'Lee',
getName() {
// 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
return this.name;
}
};
// 메서드 getName을 호출한 객체는 person이다.
console.log(person.getName()); // Lee
const person = {
name: 'Lee',
getName() {
return this.name;
}
};
const anotherPerson = {
name: 'Kim'
};
// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;
// getName 메서드를 호출한 객체는 anotherPerson이다.
console.log(anotherPerson.getName()); // Kim
// getName 메서드를 변수에 할당
const getName = person.getName;
// getName 메서드를 일반 함수로 호출
console.log(getName()); // ''
// 일반 함수로 호출된 getName 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
// 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 ''이다.
// Node.js 환경에서 this.name은 undefined다.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function () {
return this.name;
};
const me = new Person('Lee');
// getName 메서드를 호출한 객체는 me다.
console.log(me.getName()); // Lee
// getName메서드를 호출한 객체는 me이다. 따라서 getName메서드 내부의 this는 me를 가리키며 this.name값은 Lee다.
Person.prototype.name = 'Kim';
// getName메서드를 호출한 객체는 Person.prototype이고, 객체이므로 직접 메서드를 호출할 수 있다.
// 따라서 getName메서드 내부의 this는 Person.prototype을 가리키며 this.name값은 Kim이다.
// getName 메서드를 호출한 객체는 Person.prototype이다.
console.log(Person.prototype.getName()); // Kim
생성자 함수를 호출할 때, 생성자 함수로 생성될 인스턴스에게
this binding
이된다.
// 생성자 함수
function Circle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 반지름이 5인 Circle 객체를 생성
const circle1 = new Circle(5);
// 반지름이 10인 Circle 객체를 생성
const circle2 = new Circle(10);
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20
위 메서드들은 Function.prototype
의 메서드로 모든 함수가 상속받아 사용할 수 있다.
apply()
와 call()
메서드는 함수가 this binding
을 진행할 수 있게 해준다.
/**
* 주어진 this 바인딩과 인수 리스트 배열을 사용하여 함수를 호출한다.
* @param thisArg - this로 사용할 객체
* @param argArray - 함수에게 전달할 인수 리스트의 배열 또는 유사 배열 객체
* @returns 호출된 함수의 반환값
*/
Function.prototype.apply(thisArg[, argsArray])
/**
* 주어진 this 바인딩과 ,로 구분된 인수 리스트를 사용하여 함수를 호출한다.
* @param thisArg - this로 사용할 객체
* @param arg1, agr2, ... - 함수에게 전달할 인수 리스트
* @returns 호출된 함수의 반환값
*/
Function.prototype.call(thisArg[, arg1[, arg2[, ...]]])
function getThisBinding() {
return this;
}
// this로 사용할 객체
const thisArg = { a: 1 };
console.log(getThisBinding()); // window
// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg)); // {a: 1}
console.log(getThisBinding.call(thisArg)); // {a: 1}
apply
와 call
메서드의 본질적인 기능은 동일하다. 다만 인수를 전달하는 방식만 다르다.function getThisBinding() {
return this;
}
const thisArg = { a: 1 };
console.log(getThisBinding()); // window
console.log(getThisBinding.bind(thisArg)); // getThisBinding, 함수 실행되지 않음
console.log(getThisBinding.bind(thisArg)()); // {a: 1}
this binding
과 동시에 함수를 실행할 의도라면 apply
나 call
을 사용할 수 있고,bind()
를 사용해도 좋을 것이다.this
를 이용해서 코드의 흐름을 제어하기 쉽기 때문이다. 함수호출방식 | this 바인딩 |
---|---|
일반 함수 호출 | 전역객체 |
메서드 호출 | 메서드를 호출한 객체 |
생성자 함수 호출 | 생성자 함수가 (미래에) 생성할 인스턴스 |
Function.prototype.apply/call/bind 메서드에 의한 간접호출 | Function.prototype.apply/call/bind 메서드에 첫번째 인수로 전달한 객체 |