this 는 일반적으로 객체지향 프로그래밍 언어에서 사용되는 예약어이다. 객체 내부에서 자신이 속한 객체를 참조할 수 있어야 그 객체의 내부의 변수나 함수를 참조할 수 있기 때문에 이 예약어가 필요하다.
자바스크립트는 함수 호출 방식에 따라 객체가 참조하는 this의 내용이 달라진다.
객체 리터럴은 함수를 통해서 만들어지지 않는다. 때문에 this 바인딩 대신 자신의 식별자를 내부에서 호출하는 것을 통해 자기 참조를 할 수 있다.
const circle = {
radius : 5,
getDiameter(){
return 2 * circle.radius;
}
}
위 처럼 객체 리터럴은 자신의 식별자를 호출함으로써 자기 자신의 프로퍼티를 참조할 수 있다.
하지만 함수의 경우 인스턴스를 만들어내기 전까지 식별자를 참조할 방법이 없기 때문에, 따로 자기 참조를 위한 예약어가 필요하다. 이것이 this이다.
생성자 함수는 인스턴스를 만들어내는 함수이다.
function Circle (){
radius : 5
}
Circle.prototype.getDiameter() = function () {
return 2 * this.radius;
}
위 코드에서의 생성자 함수의 메서드는 this가 없으면 자기 참조가 불가능하다. 왜냐하면 바인딩은 호출 될 때 동적으로 일어나기 때문에, 인스턴스화 되기 전에는 바인딩이 되지 않는데, 위 코드에서는 인스턴스화가 되기 이전이기 때문에, 바인딩 할 인스턴스를 찾을 방법이 없기 때문이다. 그러므로 this 를 통해 호출된 이후 만들어진 인스턴스에 바인딩 시켜주는 방식을 택한다.
생성자 함수에서 this 바인딩은 만들어지는 인스턴스와 일어난다.
위 코드에
function Circle (){
radius : 5
}
Circle.prototype.getDiameter() = function () {
return 2 * this.radius;
}
const circle = new Circle(5);
circle 인스턴스를 만들어줄 때, 생성자 함수의 this 는 circle 인스턴스와 바인딩되어 해당 인스턴스를 가리키게 된다. 이를 통해 인스턴스 내부 메서드는 객체 내부 프로퍼티에 접근할 수 있다.
일반함수에서 this 바인딩은 전역 객체와 일어난다. 일반함수는 특정 객체를 만들어내는 함수가 아니다. 때문에 인스턴스가 만들어지지 않고, 바인딩할 객체가 없기 때문에, 최상위 객체인 전역 객체와 바인딩되게 된다. 일반함수의 this 바인딩은 바람직한 바인딩은 아니다. 그래서 strict mode에서는 일반함수에서 this 바인딩이 undefined와 일어난다.
function circle (){
var radius = 5;
console.log(this); // window
return this.radius;
}
위의 코드는 5를 리턴하지 않는다. 잘못 생각하면 this 가 함수를 가리켜, 함수의 radius를 반환한다고 착각하기 쉽다.
하지만 일반함수의 this는 전역객체와 바인딩되어 undefined 를 리턴하게 된다.
객체 내부의 메서드를 호출하는 경우가 있다.
const circle = {
radius : 5,
getDiameter () {
return this.radius;
}
}
circle.getDiameter(); // 5가 리턴
타 객체지향 프로그래밍 언어에서 사용되는 일반적인 this 바인딩 방식이다. 객체 내부의 프로퍼티를 참조하기 위해 자신이 속해있는 객체 참조값을 this와 바인딩한다. 그러므로 해당 메서드를 호출한 객체를 참조 하게 된다.
자바스크립트의 다양한 함수 패턴을 사용하다보면 this 바인딩이 내가 의도한대로 되지 않는 경우가 있다. 이런 경우에는 직접 this에 바인딩 시켜줄 수 있어야 한다.
이런 기능이 Function.prototype 즉, 함수 빌트인 객체의 프로토타입에 apply, call, bind 메소드가 구현되어 있다.
apply 메서드와 call 메서드의 본질적인 기능은 함수를 호출하는 것이다. 이 과정에서 this 바인딩을 변경해줄 수 있다.
function getBinding(a, b){
return this;
}
const thisArg = {a:1};
getThisBinding.apply(thisArg, [a, b]);
getThisBinding.call(thisArg, a, b);
둘은 함수 내부에서 인수로 받은 객체에 this를 바인딩해주는 기능은 똑같다. 하지만 만약 함수에 매개변수가 주어져 있을 경우, 배열을 이용해 인수를 넘겨줄지, 풀어서 넘겨줄지의 차이가 있다. 위 예시에서 getBinding 함수의 this는 전역객체가 아닌 thisArg 객체와 바인딩되게 된다.
bind도 apply 와 call 과 마찬가지로 바인딩을 위해 사용된다는 점에서 동일하다. 하지만 apply 와 call 메서드는 함수를 호출한다고 했다. bind는 함수를 호출하지 않는다. 때문에 함수 자체를 바인딩해서 넘겨주어야 할 때 유용하게 쓰인다. 특히 콜백함수, 비동기 처리를 할 때.
const person = {
name: "Lee",
foo(callback){
setTimeout(callback, 1000);
}
}
person.foo(function() {
console.log(`hello ${this.name}`);
}}
위 예시는 foo 메서드를 호출하고 그 인수로 콜백함수를 넘겨주고 있다. 콜백함수의 특징은 고차함수가 함수의 호출 시점을 결정할 수 있다는 것이다. 때문에 위의 함수에서는 this 바인딩을 따로 해주지 않으면, 콜백함수의 this 가 전역객체를 가리키게 된다. 이는 setTimeout 함수의 특징이기도 한데, 자바스크립트는 기본적으로 싱글스레드 언어이다. 때문에, 코드는 순차적으로 실행되고 멈추지 않는다. setTimeout은 1초뒤에 콜백함수를 실행하라는 것이다. 자바스크립트는 해당 함수가 호출되면 이벤트 루프에 콜백함수를 넘겨주고 모든 코드를 실행한다. 그렇게 되면 1초 뒤에는 모든 실행컨텍스트가 종료되고 전역객체만 남아있게 된다. 이렇게 되면 콜백함수가 실행되는 시점에 this는 가리킬 상위 객체가 전역객체만 남아있게 되고, 이 전역객체와 바인딩되게 된다.
const person = {
name: "Lee",
foo(callback){
setTimeout(callback.bind(this), 1000);
}
}
person.foo(function() {
console.log(`hello ${this.name}`);
}}
때문에 위 예시처럼 콜백함수에 현재 객체의 this 바인딩을 해주는 것을 통해 현재 콜백함수가 실행하면서 참조해야하는 객체를 살려두어야 한다.
function objFunction() {
console.log('Inside `objFunction`:', this.foo); // 13
return {
foo: 25,
bar: function() {
console.log('Inside `bar`:', this.foo); // 25
},
};
}
objFunction.call({foo: 13}).bar();
this 바인딩이 멘탈을 박살내놓는 예시이다. 먼저 objFunction은 일반함수로 호출되었지만, call 바인딩으로 인해 가장 상위 코드의 this는 전달된 인수인 { foo : 13 } 으로 바인딩 된다. 그리고 이 함수는 객체를 리턴한다. 이 리턴된 객체의 bar() 메서드를 호출하게 되고, 이것은 메서드를 호출한 것이기 때문에 이 메서드의 this는 해당 객체를 가리키게 된다. 그래서 25를 출력한다.
극단적인 예시이기는 하지만, 한 함수 내에서 this 바인딩이 다르게 적용되는 것은 제대로 자바스크립트를 알지 않으면 멘탈을 깨놓는 문법이 될 수 있다.