Ayden's journal

what is this

[ this is this ]라는 포스트에서 이야기했던 것처럼 나는 this가 어떻게 바인딩되는지 정확히 알지 못했고, 그로 인해 꽤 오래 고통받았다. 이번 포스트에서는 자바스크립트에서 this가 정확히 어떻게 바인딩되는 지를 알아보고자 한다.

 

this

자바스크립트에서 this는 기본적으로 실행 컨텍스트가 생성될 때 함께 결정된다. 실행 컨텍스트는 함수를 호출할 때 생성되므로, 결국 this는 함수를 호출할 때 결정된다고 할 수 있다. 함수를 호출하는 방식에 의해 같은 함수라 해도 this가 가리키는 값은 달라질 수 있다. 전역 공간에서 함수를 호출하거나, 메소드 내부에서 함수를 호출하는 경우라면 this는 전역 객체를 가리킨다. 만약 메소드 내부에서 함수를 호출할 때 this가 전역 객체가 아닌 상위 컨텍스트를 가리키게 하고 싶다면 아래와 같은 우회법을 사용할 수 있다.

const obj = {
num: 0,
plus() {
const self = this;
(function() {
if (self.num >= 100) self.num = 100
else self.num += 1
})()
return self.num
}
}

self라는 변수를 도입하는 것 말고도 전역 객체 우회 방법에는 이후에 다뤄볼 call,bind,apply 메소드와, 화살표 함수를 사용하는 방법이 존재한다. 화살표 함수의 경우 실행 컨텍스트가 생성될 때 this 바인딩 과정이 아예 생략되므로, 일반 함수처럼 호출 방법에 따라 this가 동적으로 바뀌지 않는다. 대신, 함수가 선언된 위치의 스코프에서 사용 가능한 this를 참조한다.

따라서 화살표 함수로 클래스의 메서드를 만들면 this가 언제나 인스턴스를 가리키게 할 수 있으며, 정적 메서드일 경우라면 this는 언제나 클래스를 가리키게 된다. [ this is this ]에서도 나는 이 방식을 통해 언제나 this가 인스턴스를 가리키게 만들었다. 메소드 내부의 this는 해당 메서드를 소유한 객체, 즉 해당 메서드를 호출한 객체에 바인딩된다. 다만, 이 경우에도 화살표 함수로 선언된 메소드라면 어김없이 상위 스코프의 this를 참조한다.

스코프는 실행 (혹은 전역) 컨텍스트에 따른 스코프 체인을 의미하는 만큼, 아래와 같은 객체에서는 this가 전역 객체를 바라보게 된다.

const a = {
a: 1,
setA: (a) => {
this.a = a;
return this;
},
getA: () => {
return this.a;
}
}

 

프로토타입에서의 this는 위에서 살펴본 함수 및 메서드 호출에서의 this 바인딩과 정확히 동일하게 동작한다. 인스턴스가 호출하는 경우라면 this는 인스턴스를 가리키며, 던더 프로토가 호출한다면 this는 던더 프로토를 가리킨다. 만약 호출 주체가 없다면 전역 객체를 가리킨다.

아래의 예시를 살펴보면 전역 객체와 던더 프로토에는 name 프로퍼티가 없기 때문에 undefined가 나오는 반면, 인스턴스가 호출하는 경우에는 정상적으로 이름이 출력되는 것을 확인할 수 있다.

function Person(name: string, age: number) {
this.name = 'John';
this.age = 25;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
}
const person = new Person();
person.sayHello(); // Hello, my name is John
person.__proto__.sayHello() // Hello, my name is undefined

 

call, apply, bind

call, apply, bind는 JavaScript에서 함수 호출 방식을 제어할 때 사용되는 메서드이다. 이들 모두 함수의 this 값을 명시적으로 설정할 수 있게 해주지만, 사용 방식에 있어서는 다소 차이가 있다. call은 주어진 this 값과 인수들을 사용하여 함수를 즉시 호출한다. apply도 동일하지만, 인수를 배열로 처리한다는 차이가 있다.

obj.foo.call(this, name, age)
obj.foo.apply(this, [name, age])

반면에 bind는 함수를 즉시 실행하지 않고 새로운 함수를 반환한다. 반환된 함수는 지정된 this와 인수들을 가지고 호출된다.

const newFoo = obj.foo.bind(this, name, age)
newFoo()
블로그의 프로필 사진

블로그의 정보

Ayden's journal

Beard Weard Ayden

활동하기