내가 이해한대로 정리한 내용이니 정확하지 않을 수 있다. 😉
this
는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가르키는 자기 참조 변수이다.
this
를 통해 자신이 속한 객체 또는 자신이 생성할 인스턴스의 프로퍼티나 메서드를 참조할 수 있다.
함수를 호출하면 arguments
객체와 this
가 암묵적으로 함수 내부에 전달된다. arguments
객체를 지역 변수처럼 사용할 수 있는 것처럼 this
도 지역 변수처럼 사용할 수 있는데, this
가 가르키는 값, 즉 this
바인딩은 함수 호출 방식에 의해 동적으로 결정된다.
const circle = {
radius: 5,
getDiameter() {
return 2 * this.radius;
}
};
console.log(circle.getDiameter()); // 10
객체 리터럴의 메서드 내부에서의 this
는 메서드를 호출한 객체, 즉 circle
을 가르킨다.
function Circle(radius) {
this.radius = radius; // this는 생성자 함수가 생성할 인스턴스를 가르킨다.
}
Circle.prototype.getDiameter = function() {
return 2 * this.radius; // this는 생성자 함수가 생성할 인스턴스를 가르킨다.
};
const circle = new Circle(5);
console.log(circle.getDiameter()); // 10
생성자 함수 내부의 this
는 생성자 함수가 생성할 인스턴스를 가르킨다.
자바스크립트의 this
는 다른 언어와는 다르게 바인딩이 동적으로 결정된다.
// 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);
return this.name;
}
};
console.log(person.getName());
function Person(name) {
// 생성자 함수 내부에서 this는 생성자 함수가 생성할 인스턴스를 가르킨다.
this.name = name;
console.log(this); // Person {name: "Lee"}
}
const me = new Person('Lee');
this
는 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 일반적으로 객체의 메서드 내부 또는 생성자 함수 내부에서만 의미가 있다.
일반함수에서는 의미 ✖️, 일반함수에서는 전역객체 window가 바인딩 된다.
this
바인딩은 함수 호출 방식, 즉 함수가 어떻게 호출되었는지에 따라 동적으로 결정된다.
함수의 호출 방식으로는 4가지가 있다.
Function.prototype.apply/call/bind
메서드에 의한 간접 호출기본적으로 this
에는 전역 객체가 바인딩된다.
function foo() {
console.log("foo's this: ", this); // window
function bar() {
console.log("bar's this: ", this); // window
}
bar();
}
foo();
일반 함수로 호출하면 함수 내부의 this
에는 전역 객체가 바인딩된다. 다만 일반 함수에서 this
는 의미가 없다.
// 전역 변수 value 선언
// var 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티이다.
var value = 1;
// const 키워드로 선언한 전역 변수 value는 전역 객체의 프로퍼티가 아니다.
const obj = {
value: 100,
foo() {
console.log("foo's this: ", this); // {value: 100, foo: f}
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();
콜백 함수가 일반 함수로 호출되어도 콜백 함수 내부의 this
에는 전역 객체가 바인딩된다.
var value = 1;
const obj = {
value: 100,
foo() {
console.log("foo's this: ", this); // {value: 100, foo: f}
// 콜백 함수 내부의 this에는 전역 객체가 바인딩된다.
setTimeout(function() {
console.log("callback's this: ", this); // window
console.log("callback's this.value: ", this.value); // 1
}, 100);
}
};
obj.foo();
이처럼 일반 함수로 호출된 모든 함수 ( 중첩함수, 콜백함수 포함 ) 내부의 this
에는 전역 객체가 바인딩된다.
그러나 메서드내의 중첩함수와 콜백함수는 외부함수를 도와주는 헬퍼 함수의 역할을 하는데 여기서 this
가 전역객체를 바인딩하는 것은 문제가 된다.
예제를 통해 바인딩을 일치시키는 방법을 알아보자.
var value = 1;
const obj = {
value: 100,
foo() {
// this 바인딩을 변수 that에 할당한다.
const that = this;
setTimeout(function() {
// 콜백 함수 내부에서 this 대신 that을 참조한다.
console.log(that.value);
}, 100);
}
};
obj.foo();
이 방법 외에도 this
를 명시적으로 바인딩할 수 있는 Function.prototype.apply
, Function.prototype.call
, Function.prototype.bind
메서드가 있다.
var value = 1;
const obj = {
value: 100,
foo() {
setTimeout(function() {
console.log(this.value);
}.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
에는 메서드를 호출한 객체가 바인딩 되는데, 주의할 점은 메서드 내부의 this
는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다는 것이다.
const person = {
name: 'Lee',
getName() {
// 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
return this.name;
}
};
console.log(person.getName()); // Lee
person
객체의 getName
프로퍼티가 가르키는 함수 객체는 person
객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체이다.
따라서 getName
메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.
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과 같다.
따라서 메서드 내부의 this
는 프로퍼티로 메서드를 가르키고 있는 개체와는 관계가 없고 메서드를 호출한 객체에 바인딩된다.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
};
const me = new Person('Lee');
// getName 메서드를 호출한 객체는 me다.
console.log(me.getName()); // (a) Lee
Person.prototype.name = 'Kim';
// getName 메서드를 호출한 객체는 Person.prototype 이다.
console.log(Person.prototype.getName()); // (b) 'Kim'
(a) 의 경우 getName
메서드를 호출한 객체는 me
이기에 this
는 me
를 가르키며 this.name
은 Lee
이다.
(b) 의 경우 getName
메서드를 호출한 객체는 Person.prototype
이기에 this
는 Person.prototype
을 가르키며 this.name
은 Kim
이다.
생성자 함수 내부의 this
에는 생성자 함수가 생성할 인스턴스가 바인딩된다.
function Circle(radius) {
// 생성자 함수 내부의 `this` 는 생성자 함수가 생성할 인스턴스를 가르킨다.
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
연산자와 함께 호출하지 않으면 일반 함수로 동작한다.
apply
, call
, bind
메서드는 Function.prototype
의 메서드이다. 이들 메서드는 모든 함수가 상속받아 사용할 수 있다.
apply
, call
메서드는 this
로 사용할 객체와 인수 리스트를 인수로 전달받아 함수를 호출한다.
function getThisBinding() {
return 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() {
console.log(arguments);
return this;
}
// this로 사용할 객체
const thisArg = {a : 1};
// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg, [1, 2, 3]));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f] {a : 1}
// call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달한다.
console.log(getThisBinding.call(thisArg, 1, 2, 3));
// Arguments(3) [1, 2, 3, callee: f, Symbol(Symbol.iterator): f] {a : 1}
apply
메서드는 호출할 함수의 인수를 배열로 묶어 전달한다. 반면에 call
메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달한다.
두 메서드의 동작은 차이가 없으며 대표적인 용도로는 arguments
객체는 배열이 아닌 유사 배열 이기 때문에 배열의 메서드를 사용할 수 없지만 apply
와 call
메서드를 이용하면 가능하다.
function convertArgsToArray() {
console.log(arguments);
// 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]
Function.prototype.bind
메서드는 apply
와 call
메서드와 달리 함수를 호출하지 않고 this
로 사용할 객체만 전달한다.
function getThisBinding() {
return this;
}
// this로 사용할 객체
const thisArg = {a : 1};
// bind 메서드는 함수에 this로 사용할 객체를 전달한다.
// bind 메서드는 함수를 호출하지는 않는다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)());
bind
메서드는 메서드의 this
와 메서드 내부의 중첩 함수 또는 콜백 함수의 this
가 불일치하는 문제를 해결하기 위해 사용되기도 한다.
const person = {
name: 'Lee',
foo(callback) {
setTimeout(callback, 100);
}
};
person.foo(function() {
console.log(`Hi! my name is ${this.name}.`); // Hi! my name is .
// 일반 함수로 호출된 콜백 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
// Node.js 환경에서 this.name 은 undefined 이다.
});
위 예제를 bind
메서드를 사용하여 this
를 바인딩 해보자.
const person = {
name: 'Lee',
foo(callback) {
setTimeout(callback.bind(this), 100);
}
};
person.foo(function() {
console.log(`Hi! my name is ${this.name}.`);
}); // Hi! my name is Lee.
this
바인딩이 호출 방식에 따라 달라지는 것을 정리해 보자.
Function.prototype.apply/call/bind
메서드에 의한 간접 호출 ➡️ Function.prototype.apply/call/bind
메서드의 첫번째 인수로 전달한 객체끝