All Articles

JavaScript의 this

this는 평소엔 딱히 어렵지 않다고 느껴지면서도, 오픈소스 등 남의 코드를 읽을 때에는 걸림돌이 되곤 한다. 이는 내가 this를 제대로 알지 못하기 때문인 걸로 느껴져서, 좀 더 공부해보기로 했다.

메서드 호출에서의 this

this는 현재 실행 컨텍스트이다. 아래 예시를 보자.

alert(this === window); // (1) 여기서 this는 window 객체, 즉 true

const caller = {
  test: function() {
    alert(this === window);
  },
}
caller.test(); // (2) 여기서 this는 caller 객체, 즉 false
  • 참고로, (1)은 함수 호출, (2)는 메서드 호출이라고 한다. 함수와 메서드를 혼용해서 쓰는 경우도 있는데, 이처럼 엄밀히는 다르다.
  • 하지만, 실제로 (1)의 케이스를 메서드 호출이라고 볼 수도 있다. 따지고 보면 window.alert(this === window);와 동일하기 때문이다.
  • 단, strict(엄격)모드에서는 이 둘을 다르게 보긴 한다. strict일 때에는 함수 호출로 하면 thisundefined가 된다.

이번엔 아래 예시를 보자.

const testObj = {
  outerFunc: function() {
    function innerFunc() {
      console.log(this) // window
    }
    innerFunc()
  },
}
testObj.outerFunc()
  • innerFunc가 호출될 때에는 아무 컨텍스트도 없다(바인드 되지 않았다). 따라서, 비엄격모드 기준으로 thiswindow가 되는 것이다. 물론 엄격모드에서는 undefined일 것이다.
  • 반면, outerFunc의 this는 testObj이라는 객체가 되겠다.

생성자 함수 / 객체 내에서의 this

  • new 키워드를 통해 만들어지거나 리터럴 표기에 의해 만들어진 객체에서, this는 해당 객체를 가리킨다.
  • this는 보통 동적으로 결정되지만, 객체 내 메서드의 this는 정적(Lexical)으로 결정되어 해당 메서드를 갖고 있는 객체가 된다.
  • 이는 다른 언어에서의 보편적인 this와 유사하므로 넘어가겠다.

이벤트 리스너에서의 this

  • 이벤트 리스너에서 this는 이벤트를 발생시킨 객체이다.

컨텍스트가 없을 때 this를 정상적으로 쓰는 방법

객체 메서드가 객체 내부가 아닌 다른 곳에 전달되어 호출되면 this가 사라진다. 콜백함수를 쓰이는 경우, 클릭 이벤트 등으로 함수가 전달되는 등 케이스에서는 기존 this와의 연결이 끊기는 것이다. 뿐만 아니라, 함수 안에 함수가 존재하는 경우(앞선 예시에서 innerFunc)에는 this를 정상적으로 쓸 수 없었다. 어떻게 해결하면 될까?

this를 상수(const)로 저장해두기

  • 간혹 this가 제대로 먹히지 않아, 어찌저찌 this를 예컨대 self(물론 이 이름은 변경 가능)라고 두어 코드를 정상 작동시켰던 경우가 있었다. 그동안 난 원리를 잘 알지 못했으나, 이번에 알게 되었다.
  • this를 상수로 지정했기 때문에 아래와 같이 서브루틴 안에서 이 this를 찾을 수 있게 되는 것이다.(this가 freezing 되어버리는 느낌)

    function Family(firstName) {
    this.firstName = firstName
    const names = ['Seungwoo', 'Hyunsang', 'Zzanga']
    const self = this
    names.map(function(value, index) {
      console.log(value + ' ' + self.firstName)
    })
    }
    const hansFamily = new Family('Han')
    // Seungwoo Han
    // Hyunsang Han
    // Zzanga Han

bind 메서드 쓰기

  • 매번 상수화하기는 좀 귀찮을 수 있다. 함수가 겹겹이 있을 수 있고, 그 때마다 상수의 이름을 정하는 것부터가 좀 그렇다.
  • 따라서 조금 더 나은 방법으로, bind메서드를 써서 서브루틴에 영구적으로 this를 고정시킬 수 있다.

    function Family(firstName) {
    this.firstName = firstName
    const names = ['Seungwoo', 'Hyunsang', 'Zzanga']
    names.map(function(value, index) {
      console.log(value + ' ' + this.firstName)
    }.bind(this))
    }
    const hansFamily = new Family('Han')
    // Seungwoo Han
    // Hyunsang Han
    // Zzanga Han
  • 사실, React.js를 쓰면서 뭔지도 모르고 .bind(this)를 쓰고 있는 경우가 많을 것이다. 이번 기회에 알고 쓰자.

화살표함수 쓰기

  • bind를 많은 사람들이 이해하기 어려워하기 때문에 화살표함수가 더 사랑받는 것도 있는 것 같다.(화살표함수는 곧 따로 다루어보도록 하겠다.)
  • 일반적으로 this는 동적 스코프에 의해 결정되지만, 화살표함수를 썼을 때에는 자신을 둘러싼 정적(Lexical) 스코프에 의해 this가 결정되게 된다.
  • <모던 자바스크립트 튜토리얼>이라는 오픈소스 프로젝트의 한글판에 Contribution했을 때 이에 대해 다루면서 좀 더 알게 되었는데, this를 bind하는 방법과 이렇게 화살표함수를 쓰는 방법은 엄밀히 말하면 조금 다르다고 한다. bind(this)를 했을 때 this는 아예 한정이 되어버리는 것이고, 화살표함수 안에서의 this는 마치 일반적인 변수가 상위 렉시컬 환경에서 변수의 검색이 이루어지는 것처럼, 상위 렉시컬 환경에서 this를 찾게 되는 것이다.

    function Family(firstName) {
    this.firstName = firstName
    const names = ['Seungwoo', 'Hyunsang', 'Zzanga']
    names.map((value, index) => {
      console.log(value + ' ' + this.firstName)
    })
    }
    const hansFamily = new Family('Han')
    // Seungwoo Han
    // Hyunsang Han
    // Zzanga Han
  • 참고로 이 화살표함수가 포함된 코드를 babel로 트랜스파일링 해보면, this를 const를 통해 저장해두는 것과 똑같이 변환됨을 알 수 있다. 그렇다, 화살표함수는 상위 렉시컬 환경에서 this를 찾아 상수화하여 쓰는 방식으로 작동하는 것이다.(다시 말해, 앞서 소개한 ‘this를 상수로 저장해두기’와 ‘bind 메서드 쓰기’는 사실상 같은 거나 다름 없다는 것이다!)

[참고자료]
https://blueshw.github.io/2018/03/12/this/
https://ko.javascript.info/arrow-functions