this in JavaScript

CDD·2023년 3월 16일
0

web-develop

목록 보기
6/11
post-thumbnail

Intro

객체 지향 언어를 다뤄보았다면 한 번 즈음은 들어봤을 만한 단어 this, 주로 클래스 내에서 자기 자신을 가리키기 위해 사용하는 방식으로 알고 있었다. 그런데 JS에서는 종종 다른 느낌을 받아서, 이번에 공부를 통해 알게된 this 연산자의 사용에 대한 여러가지 케이스들을 작성해보려고 한다.

기존에 알고 있던 일반적인 this

class Person {
  height;
  weight;
  constructor(h, w) {
    this.height = h; // 객체 자기 자신을 가리킬 때 쓰이는 this
    this.weight = w;
  };
}

const p1 = new Person();

기존에 알고 있던 가장 보편적인 this의 쓰임이다. class의 목적 중에 캡슐화, 은닉화 정도를 위해 다른 언어들에서는 내부 변수를 private 선언하며 get(), set() 함수를 정의해 외부 스코프에서도 클래스의 내부 변수에 접근하는 방법이 있다. 여기서 메서드 하나를 만들어보자.

class Person {
  ... // height, weight, constructor
  heightAverageDifference(average) {
    return this.height - average
  }
}

클래스 속성의 height 값에 평균치를 뺀 편차를 반환하는 함수이다. thisPerson으로 만들어진 하나의 객체를 가리키기 때문에 내부 선언된 height, weight 값에 접근할 수 있다는 특성이 있다. 여기까지가 기존에 알던것이고, 이 다음부터 나오는 것은 모르고 있었던 JS 내의 this 사용 예시이다.

함수 스코프에서의 this

일반 함수에서의 this

function returnBMI (h, w) {
  let height;
  this.height = h;
  return w / height ** 2;
}

returnBMI(1.7, 70)// Error

육안으로 봤을 때는 정상적으로 작동할 것 같지만 이 코드는 그럴수가 없다. 여기서 사용되는 this는 당연히 함수 본인 returnBMI()를 가리킬 것 같지만, 실제로는 다른 곳을 가리키고 있다. 함수 내 사용되는 this는 함수를 호출한 곳을 가리키는데, 인터넷을 서칭하다보니 함수에서 실행된 컨텍스트 내(object, class 어디에도 바인딩 되지 않은 케이스this는 거의 무조건적으로 global scope을 가리킨다고 한다.

nodeJS VS Browser Console

function thisIsGlobal () { console.log(this); }
thisIsGlobal();

임의의 함수를 만든 후, 실행을 해보면 다음과 같은 결과가 나온다.

<ref *1> Object [global] {
  global: [Circular *1],
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  structuredClone: [Getter/Setter],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Getter]
  },
  atob: [Getter/Setter],
  btoa: [Getter/Setter],
  performance: [Getter/Setter],
  fetch: [AsyncFunction: fetch],
  crypto: [Getter]
} // nodeJS 환경에서 실행 시

Window {0: Window, window: Window, self: Window, document: document, name: '', location: Location,} // 브라우저 개발자 도구의 console에서 실행시

둘 다 global을 가리킬텐데 왜 결과가 다르게 나오는지에 대한 의문이 들 수 있다. 바로 설명하자면 nodeJS에서는 다음과 같이 전역 스코프가 구성되어 있고, 브라우저 같은 경우 Window가 전역 객체를 의미하기 때문에 Window를 출력한다. 실행 컨텍스트의 call stack 구조를 보면 초기 상태에서 thisWindow를 가리키고 있는 이유이기도 하다. 브라우저 콘솔에 다음과 같은 코드를 쳐보면 알 수 있다.

$ var a = 10;
$ let b = 20;
$ console.log(window.a); // 10
$ console.log(window.b); // Error

Window가 전역 객체이기 때문에 console.log(window.b)도 정상 작동해야 할 것 같지만, 관련해서 정보를 찾아보니까 Window 객체 내부에 보이지 않는 개념적인 블록 스코프가 존재한다고 한다. letconst는 블록 스코프 내에서만 유효하므로 window에서 접근을 할수가 없는 것이다. 사실 실사용이 자주 될 것 같지 않은 개념이기는 한데, this가 전역 스코프를 가리킨다는 내용은 분명 중요하다.

Class와 Object 내에서의 this

const personObject = {
  function thisFunction () {
    console.log(this); // personObject 객체
  },
  thisArrowFunction = () => {
    console.log(this); // globalScope
  }
}

class personClass {
  function thisFunction () {
    console.log(this);
  }; // personClass 객체
  thisArrowFunction = () => {
    console.log(this);
  }; // personClass 객체
}

헷갈릴수도 있는 코드이기도 한데, 출력 값은 주석을 참고하면 될 것 같다. 오브젝트 내에서의 this 같은 경우 일반 함수에서 쓰였을 때는 스코프체인으로 묶여있는 Object를 가리킨다. 화살표 함수 같은 경우 그렇지 않고, 전역 스코프를 가리키는데 자신이 생성된 환경을 가리키기 때문이다. 이것에 대해서 좀 더 잘 이해하기 위해서는 Lexical Enviroment / Scope에 관련해서 찾아보면 될 것이다. 반면 class는 모든 this가 객체 자신을 가리키는데, 정확히 어떤 스코프 차이가 있는지는 잘 모르겠지만 뭐 그렇다. 여러 번 코드를 작성해보면 this가 어디를 가리키고 있는지에 대한 감이 올 것이다.

Class, Object 내의 메서드 내의 메서드가 가리키는 this

class personClass {
  function f1() {
    function f2() { console.log(this); }
  }
}

다음과 같이 되어 있는 코드에서 "this는 당연히 personClass를 가리키겠지" 라고 생각했다면 오답이다. 함수를 실행시켜보면 this는 전역을 가리키는 것을 알 수 있는데, 함수 컨텍스트에서 쓰인 this는 무조건 전역을 가리킨다. this의 시점이 함수 실행이면 사실 상 전역을 가리킨다고 보면 된다. 그렇다면 메서드 내의 함수에서 this가 전역을 가리키게 만드려고 하면 어떻게 해야 할까? 바로 화살표 함수를 쓰면 된다.

화살표 함수의 this VS 일반 함수의 this

class personClass {
  function f1() {
    function f2() { console.log(this); } // 전역
  }
  function f3() {
    f4 = () => { console.log(this); } // personClass 객체
  }
}

기본적으로 f1()이나 f3()에서 this를 사용한다면 모두 동일하게 personClass를 가리키지만 그 안의 f2()f4()에서 쓰이는 this는 각기 다른 곳을 가리킨다. arrow function을 사용하면 함수에서의 실행 컨텍스트 개념이 아니라 personClass와 바인딩 되어 있는 느낌이라 그대로 this를 사용하여 내부 값에도 접근 할 수 있다.

0개의 댓글