본문으로 바로가기

실제 있는 책이 아닙니다

 

다른 언어를 사용하다 자바스크립트로 넘어온 개발자는 this를 혼동하기 쉽습니다. this는 항상 메서드가 정의된 객체를 참조할 것이라고 착각하죠. 이런 개념을 'bound this'라고 합니다. 자바스크립트에서 this는 런타임에 결정됩니다. 메서드가 어디서 정의되었는지에 상관없이 this는 ‘점 앞의’ 객체가 무엇인가에 따라 ‘자유롭게’ 결정됩니다. 이렇게 this가 런타임에 결정되면 좋은 점도 있고 나쁜 점도 있습니다. 함수(메서드)를 하나만 만들어 여러 객체에서 재사용할 수 있다는 것은 장점이지만, 이런 유연함이 실수로 이어질 수 있다는 것은 단점입니다.

 

let user = { name: "John" };
let admin = { name: "Admin" };

function sayHi() {
  alert( this.name );
}

// 별개의 객체에서 동일한 함수를 사용함
user.f = sayHi;
admin.f = sayHi;

// 'this'는 '점(.) 앞의' 객체를 참조하기 때문에
// this 값이 달라짐
user.f(); // John  (this == user)
admin.f(); // Admin  (this == admin)

admin['f'](); // Admin (점과 대괄호는 동일하게 동작함)

 

JS에서 가장 혼란스럽고 종잡을 수 없는 개념으로 this가 있습니다. React에서 this.props, this.state와 같이 관습적으로 this를 활용하긴 했지만 정확히 이것이 무엇을 의미하는지에 대해서는 모르는 경우가 많습니다.

 

대부분의 객체지향 언어에서 this는 클래스로 생성한 인스턴스 객체를 말합니다. 클래스에서만 사용할 수 있기 때문에 this에 대해 헷갈릴 여지가 없습니다.

 

그러나 JS에서 this는 어디에서든 사용할 수 있습니다.  (런타임에 따라 달라짐) 특히, 일반 함수와 화살표 함수의 경우 this가 가리키는 대상이 달라지기도 해서 혼동이 가중됩니다. 하여튼, this를 정리해봅시다.

 

함수가 실행되서 실행 컨텍스트가 생성될 때 기본적으로 this가 결정됩니다.

즉, 함수 실행 = 실행 컨텍스트 생성 = this 결정

함수를 어떻게 호출하느냐에 따라 this의 값이 달라진다. 다양하게 실행해보면서 this값이 나오는 상황을 살펴봅시다.

 

const a = {
  name: 'darren',
  sayName: function () {
    console.log(this.name);
  },
  sayNameArrow: () => {
    console.log(this.name);
  },
};

a.sayName();
a.sayNameArrow(); // Error! 화살표 함수는 실행 컨텍스트(실행 맥락)를 만들어내지 않음! 따라서 this도 없음

 

요약

🎈 일반 함수는 호출 방식 (앞에 .이나 []을 통해 메서드 형태로 호출했는가?)에 따라 this가 가리키는 것이 달라집니다.
🎈 화살표 함수는 스코프 체인상 가장 가까운 this를 가리킵니다. (lexical this라고 합니다)

🧷 전역 공간에서의 this는 전역 객체를 참조합니다.
🧷 어떤 함수를 메서드로서 호출한 경우 this는 메서드 호출 주체(obj.func()에서 obj)를 참조합니다.
🧷 어떤 함수를 함수로서 호출한 경우 this는 전역 객체를 참조합니다. 메서드 내부 함수에서도 같습니다. 이 문제를 피하기 위해서는 this를 바인딩하거나 화살표 함수를 사용합시다.
🧷 콜백 함수 내부의 this는 해당 콜백함수의 제어하는 것을 가리킵니다. 정의되지 않으면 전역객체를 가리킵니다.
🧷 생성자 함수에서 this는 해당 생성자로 생성된 인스턴스를 참조합니다.

 

  • 전역 공간에서 this

전역 컨텍스트에서 this는 전역 객체를 가리킵니다. 브라우저 환경에서는 window이고 Node.js에서는 global입니다.

 

 

this와 상관 없지만 전역 공간의 특이한 성질 하나를 짚고 가겠습니다.
전역 변수를 선언하면 JS 엔진은 전역객체의 속성으로 할당합니다. 이는 Node.js에서도 마찬가지 입니다.
(var만 해당하며 const, let에는 해당 사항 없습니다)
var a = 1
window.a // 1
this.a // 1

 

  • 메서드로서 호출할 때 메서드 내부에서의 this => this =  호출한 객체 

 

메서드 함수를 호출하면 메서드를 호출하는 주체(여기서는 obj)를 this로 가리킵니다.

obj.test()는 obj에 대한 정보를, obj.testThis.test2()는 testThis라는 내부 객체를 가리킵니다.

 

단, 메서드 함수가 화살표 함수라면 화살표 함수는 내부에 this가 없습니다. 스코프 체인상 가장 가까운 스코프의 this에 접근하게 됩니다. 이를 lexical this라고 부릅니다.

 

 

 

  • 함수 호출에서의 this

 

그런데 만약 메서드를 객체를 거치지 않고 직접 함수를 호출하면 this는 어떻게 될까? 보시다시피 지정된 것이 없어서 그냥 window 객체를 반환한다. 여기서 알 수 있는 것은 함수 앞에 . 혹은 []로 호출했는지 안했는지가 중요합니다. 호출의 형태가 중요한 거지 함수의 내용이 중요한 게 아닙니다.

 

 

 

  • 메서드 내부에서 호출되는 경우 this 상속법 (self, _this, that)

this의 상속 문제는 이 문제는 메서드 내부에서 호출되는 경우에 종종 발생합니다. 메서드 함수 내부에서 호출되었음에도 불구하고 일반 함수는 앞에 .이나 []을 붙이지 않으므로 this가 window를 가리키게 됩니다.

const obj = {
  outer: function () {
    console.log(this); // outer 객체를 출력합니다.
    
    var innerFunc1 = function () {
      console.log(this); // window 객체를 출력합니다.
    };
    innerFunc1(); // 함수 앞에 점이나 []로 호출되지 않았으므로 this는 window를 가리킵니다.
    
    var self = this; // outer 스코프에서 this는 outer 객체를 가리킵니다. 
    
    var innerFunc2 = function () {
      console.log(self); // 앞서 저장한 self, 즉 outer 객체를 가리킵니다.
    };
    
    innerFunc2();
  },
};

obj.outer();

 

둘 다 함수 내부에서 호출된 함수임에도 불구하고 innerFunc1은 window를, innerFunc2는 obj를 가리키고 있습니다.

 

그런데, 상식적으로 생각해보면 이러한 innerFunc1의 this의 작용은 상식을 벗어난 것입니다. obj 안에서 호출했으면 당연히 this는 obj를 가리켜야 할 터인데, 앞에 아무 것도 붙이지 않았다는 이유로 this는 window 객체를 가리킵니다. 더글라스 크락포드는 이를 명백한 설계 상의 오류로 지적합니다. 하지만 JS를 아예 뜯어서 고칠 수 없기 때문에 이에 적응해야 합니다.

 

보통 함수 내부에서의 호출에서 this를 고정하기 위해서 self = this, _this = this, that = this와 같이 this를 지정한 후 this 대신 다음 값을 넘겨주곤 합니다. 변수명은 무엇이 되어도 좋지만 소통을 위해 self, _this, that을 사용하는 것이 좋습니다. 저는 self와 that을 혼용하는 편입니다.

 

이러한 this의 상속을 통해야 하는 일을 화살표 함수를 이용하면 피할 수 있습니다. 화살표 함수는 내부에 this가 없습니다. 스코프 체인상 가장 가까운 this에 접근하게 됩니다.

 

 

좌측 메서드의 경우 innerFunc의 스코프에서 가장 가까운 스코프는 obj 객체의 스코프입니다. 따라서 this는 obj를 가리킵니다. 우측 메서드의 경우도 마찬가지로 호출된 스코프에서 가장 가까운 스코프가 obj이므로 this가 obj를 가리킵니다.

 

 

  • 콜백 함수에서의 this => this = 누가 콜백 함수를 실행했는가?

 

콜백 함수는 누가 불렀느냐에 따라 this가 달라집니다. 코드를 확인해봅시다.

 

우선 윈도우 객체에 있는 setTimeout을 이용해 콜백을 실행한 결과 this는 Window를 가리켰습니다. 콜백 함수를 부른 것이 Window 객체이므로 this는 Window를 가리킵니다.

 

 

이번에는 버튼을 누를 때마다 this를 실행하는 코드를 짜보았습니다. 

 

 

실행 결과는 <button>입니다. 왜냐구요? 콜백함수를 제어하는 부분이 button이기 때문입니다. 버튼을 누르면 이벤트 콜백 함수가 실행됩니다. 따라서 this는 button입니다.

 

그런데 콜백을 화살표 함수로 사용하면 어떻게 될까요? 위 콜백을 () => console.log(this)로 바꿔서 출력한 결과는 다음과 같습니다.

 

 

  • 생성자 함수에서의 this => 인스턴스 자신

 

생성자 함수를 정의하는 부분에서는 습관적으로 this를 적어주었습니다. 생성자 함수 내부의 this는 인스턴스 자신에 해당합니다. 

const Cat = function (name, age) {
  //생성자 함수는 반드시 대문자로 시작해야 함
  this.bark = "myeyau";
  this.name = name;
  this.age = age;
};

const Hegel = new Cat("Hegel", 56);
const Ruby = new Cat("Ruby", 32);

console.log(Hegel, Ruby);

 

위와 같은 코드의 경우 Cat 생성자 함수를 통해 Hegel이란 인스턴스를 생성했고, this는 Hegel을 가리킵니다.

Ruby라는 인스턴스에서 this는 Ruby를 가리킵니다. 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체