티스토리 뷰

TIL(Today I Learn)

[JS] this에 대해서

MinsoftK 2021. 12. 11. 22:51
728x90
반응형

 

this에 대한 이해

this란 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이다.

자바스크립트에서 this에 대한 내용이 굉장히 헷갈렸다. 그 이유는 this바인딩이 상황에 따라 다르게 때문이었다. 함수를 정의할 때 this에 바인딩할 객체가 결정되는 것이 아니고, 함수를 호출할 때 함수를 어떻게 호출했는지에 따라 this에 바인딩할 객체가 결정된다. 굉장히 헷갈리는 부분이었고 나중에 화살표 함수에서도 this가 바인딩되는 방식이 달랐다. 화살표 함수에서는 함수 자체의 this 바인딩을 갖지 않는다. 그래서 상위 스코프의 this를 참한다. 이렇게 개발자가 함수를 어떻게 호출하냐에 this가 바인딩하는 위치가 달라진다.

생각해보면 굉장히 비효율적인 것 같다. 어떻게 호출되냐에 따라 this가 가리키는 객체가 달라진다는 것은 실수할 확률이 높기 때문이다. 아직은 경험이 적어 this로 인한 문제들을 마주치지는 못했지만 실수를 많이 생길 수밖에 없는 부분인 것 같다. 내용을 정리하면서 헷갈렸던 부분과 추가로 궁금한 점들을 적어봤다.

 

바인딩?
식별자와 값을 연결하는 과정. 변수 선언이 바인딩 과정이다. 변수 선언은 변수와 변수가 확보한 메모리 공간을 바인딩한다.

 

1. 객체 리터럴, 생성자 함수에서의 this

객체 리터럴

  • 객체 리터럴 방식으로 생성했을 경우는 다음과 같다. this는 메서드를 호출한 객체, 즉 person을 가리킨다.
const person = {
  name: 'minsoftk',
  getName() {
    return this.name;
  },
};

console.log(person.getName());

생성자 함수

  • 생성자 함수의 경우는 다음과 같다. 여기에서의 this는 생성자 함수가 생성할 인스턴스를 가리킨다. 
function Person(name) {
  this.name = name;
}

Person.prototype.getName = function () {
  return this.name;
};

const person = new Person('minsoftk');
console.log(person.getName());

 

이렇게 this는 상황에 따라 가리키는 대상이 다르게 되고, this 바인딩은 함수 호출 시점에 결정된다.

 

2. 함수를 호출하는 방식

함수를 호출하는 방식에 따라 this 바인딩이 달라진다고 했는데, 함수를 호출하는 방식은 다음과 같다.

  • 일반 함수 호출
  • 메서드 호출
  • 생성자 함수 호출
  • Function.prototype.apply/call/bind

 

일반 함수 호출

일반 함수로 호출하는 경우에는 전역 객체가 바인딩된다. this의 의미가 객체의 프로퍼티나 메서드를 참조하기 위한 자기 참조 변수이므로 일반 함수에서의 this는 큰 의미가 없다는 생각이 든다. 전역 객체를 가리키게 할 상황이 있을지 모르겠다.

 

메서드 호출

메서드를 호출했을 때, this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다. 처음에는 이게 무슨 말인지 이해가 잘 가지 않았다. 결국 메서드를 소유한 객체가 메서드를 호출하니깐 해당 객체로 this가 바인딩되는 게 아닌가?라는 생각을 했다. 하지만 호출한 메서드가 서로 다른 객체의 메서드가 될 수 있는 경우도 있었다. 그 경우는 다음과 같다. 

function Person(name) {
  this.name = name;
}

Person.prototype.getName = function () {
  return this.name;
};

const me = new Person('minsoftk');

console.log(me.getName()); // minsoftk

Person.prototype.name = 'minsoftk2';

console.log(Person.prototype.getName()); // minsoftk2

같은 getName 메서드이지만, 어느 객체에서 호출하냐에 따라 해당 객체에 바인딩이 된다. 해당 객체에서는 name이라는 프로퍼티 키의 값은 다를 수도 있다. 이런 경우를 생각 못해서 처음에 이해가 어려웠다.

 

생성자 함수 호출

생성자 함수 내부의 this에서는 생성할 인스턴스가 바인딩된다. 즉, 생성자 함수를 인스턴스를 생성했을 때, 해당 객체로 바인딩이 된다는 의미이다. 그런데 한 가지 의문이 생성자 함수도 function을 이용해서 객체를 생성하는 방식인데 생성자 함수랑 함수와 어떻게 구별을 하는 걸까?라는 의문이 생겼다. 

이는 new라는 연산자와 함께 사용하면 생성자 함수, new라는 연산자가 사용되지 않으면 일반 함수로 동작한다고 한다. 자바스크립트 엔진에서 구별하는 방식인 것 같다. 그래서 아래와 같이 일반 함수로 호출이 되었을 때는 undefined를 반환한다. (자바스크립트 함수에서는 return이 생략되었을 때 암묵적으로 undefined를 반환한다.)

자바스크립트에서 어떤 동작을 이해를 하려면 기본적인 내용들을 정확히 아는 게 중요했다. 함수에서도 반환문이 없을 때, 암묵적으로 undefined를 반환한다는 내용을 모르면 왜 이런 출력이 나오는지 이해하기가 어렵다. 일반 함수로 사용됐을 때는 return 반환문이 없으므로 undefined가 출력된다. 처음에 왜 undefined가 나오지? 생각하고 함수 파트를 다시 복습했다..

function Person(name) {
  this.name = name;
}

const person1 = Person('minsoftk');

console.log(person1); // undefined
console.log(name); // minsoftk

결국 생성자 함수로 호출이 됐을 때는, new라는 연산자가 붙고 인스턴스가 생성이 되게 된다. 생성된 인스턴스를 this가 가리키게 된다.

 

Function.prototype.apply/call/bind 메서드에 의한 간접 호출

27장 배열에도 유사 배열 객체를 배열로 변환할 때 사용이 된다. 유사 배열 객체를 배열로 변환한다는 부분도 아직은 정확하게 이해를 하지 못해서 추후에 배열에 관련된 포스팅을 하면서 유사 배열 객체란 무엇인지 내용을 정리할 계획이다. 

function getThisBinding() {
  console.log(arguments); // [Arguments] { '0': 1, '1': 2, '2': 3 }
  return this;
}

const thisArg = 100;
console.log(getThisBinding.apply(thisArg, [1, 2, 3])); // [Number: 100]
console.log(getThisBinding.call(thisArg, 1, 2, 3)); // [Number: 100]
console.log(getThisBinding.bind(thisArg)()); // [Number: 100]

apply, call을 쓰게 되면 getThisBinding이라는 함수의 this는 thisArg의 값으로 바인딩시켜준다. 또한 함수 객체의 arguments의 프로퍼티 값으로 객체를 전달해주게 된다. apply는 배열 형태로 전달해주고 call은 , 를 사용해서 인수로 넘겨주게 된다.

bind 같은 경우는 함수를 호출하지 않고 this로 사용할 객체만 전달한다. 따라서 호출 연산자 '( )'를 붙여줘서 함수를 호출해줘야 한다.

 

3. 결론

함수 호출하는 방법에는 4가지가 있고, 각각의 경우 this 바인딩은 어떻게 되는지 공부해봤다. 공부를 하면 할수록 모르는 부분들도 많이 생겼고, 이를 어떻게 활용할 수 있는지에 대한 고민을 많이 하게 됐다. 따라서 이런 포스팅을 통해서 빈 개념들을 잘 메꿔가야겠다는 생각이 많이 들었다.

ES6에서는 화살표 함수가 나오는데, 화살표 함수에서 this 바인딩이 또 달라진다. 이렇게 헷갈리는 this를 꼭 사용해야 되나?라는 질문을 받았는데 아직 내가 판단할 만큼의 this의 문제점을 경험해보지 못했다. 그래서 아직 판단이 되지는 않는다. 하지만 생성자 함수를 써야 한다면 인스턴스 생성을 해야 하고 인스턴스에서 this 사용은 불가피하다 생각한다. 그래서 this는 꼭 써야 되는 게 아닌가란 생각이 들었다. 하지만 이렇게 어렵게 쓸바엔 그냥 '객체 리터럴로 생성해서 편하게 사용하면 안 되나?'라는 생각은 강하게 들었다. 상황에 따라 달라질 것 같지만 가독성 측면에서는 지양해야 될 것 같다.

 

 

혹시 틀리거나 잘못된 내용이 있다면 댓글로 알려주세요. :)

 

 

📕 Reference:

모던 자바스크립트 Deep Dive - 이웅모 저 : 22장 this

https://poiemaweb.com/js-this

 

728x90
반응형
댓글