this, arrow-function

화살표 함수에서의 this는 사용이 조금 다르다. this에 대해 먼저 알아보고 화살표 함수에서는 this가 어떻게 동작하는지 알아보자.

this

자바스크립트의 함수에는 메서드가 호출된 개체를 참조하는 this라는 특수한 변수가 있다. this의 값은 함수 호출에 따라 동적으로 변하며, 이 때문에 작은 문제 상황에 직면하게 된다.

함수는 정적 범위(static/lexical scope)와 동적 범위의 두 가지 범위(scope)에서 실행된다. '정적 범위'는 함수를 둘러싸는 범위이고, '동적 범위'는 함수를 호출한 범위이다.

자바스크립트에서 함수는 생성자, 메서드(객체의 일부), 함수(서브 루틴, subroutines)의 역할을 하는데, 함수가 서브루틴(이름이 겹쳐 아래부터는 서브루틴으로 명명)의 역할을 할 때 this가 동적으로 변해 문제가 생긴다. 서브루틴이 객체에 대해 호출되지 않기 때문에 this의 값은 strict 모드에서 정의되지 않고 전역 범위로 설정된다. 이때문에 콜백함수를 사용하는게 어렵게 만든다.

아래 예제를 보자.

const person = {
  default: "Hello",
  greet: function (names) {
    names.forEach(function (name) {
      console.log(`${this.default} ${name}`);
    });
  },
};

person.greet(["world", "heaven"]);
// undefined world
// undefined heaven

위 코드를 보면 names배열에 forEach 함수에 서브 루틴을 전달하고 있다. 이때의 서브 루틴의 this값은 undefined이며, 외부 메서드인 greetthis에는 접근할 수 없다. 그래서 이 문제를 해결하기 위해서는 이 서브루틴에 정적(lexical) this가 필요하며, 변수에 this를 할당한 다음 클로저를 통해 서브루틴에 접근하는 방식을 사용해야 한다.

const person = {
  default: "Hello",
  greet: function (names) {
    const lexcicalThis = this;
    names.forEach(function (name) {
      console.log(`${lexcicalThis.default} ${name}`);
    });
  },
};

person.greet(["world", "heaven"]);
// Hello world
// Hello heaven

하지만 이런 식으로 코드를 작성한다면 문제야 해결은 되지만, 위 코드를 보는 동료들 또한 왜 이렇게 문제를 해결했는지에 대한 지식도 있어야 하며, 사이드 이펙트가 생길 여지조차 커진다.

그렇다면 위 방식 대신 화살표 함수를 사용해 보면 어떨까?

화살표 함수와 this

화살표 함수는 그 자체의 사용법을 매우 간단하다(Arrow function - MDN). 다만 화살표 함수는 화살표 함수 자신의 this 값을 가질 수 없으며, 대신 this는 화살표 함수를 둘러싸는 범위(정적 컨텍스트)의 this를 가져온다.

위의 예제를 화살표 함수를 이용하여 수정하면 아래와 같다.

const person = {
  default: "Hello",
  greet: function (names) {
    names.forEach((name) => {
      console.log(`${this.default} ${name}`);
    });
  },
};

person.greet(["world", "heaven"]);
// Hello world
// Hello heaven

this의 바인딩에 따른 분류

위 글은 this를 좀 더 이해하기 쉽게 풀어쓴 내용이다. 조금 더 정적인(?) 내용을 원한다면 아래의 내용을 참고하자.

기본 바인딩(Default binding)

  • 전역 공간에서 this는 전역 객체를 참조한다.

  • [브라우저]에서 함수가 단독 호출되면, "비엄격 모드"에서는 window가 바인딩되고, "엄격 모드"에서는 전역 객체는 바인딩 대상에서 제외되기 때문에 undefined가 바인딩 된다.

  • [Node]에서 함수가 단독 호출되면 this는 마찬가지로 전역 객체에 바인딩이 되는데, 노드 환경에서는 전역객체인 global에 바인딩이 되고, 전역 코드에서의 thisexports에 바인딩이 된다.

암시적 바인딩(Implicity binding)

  • 함수를 메서드로서 호출한 경우 this 는 메서드 호출 주체(메서드명 앞에 객체)를 참조한다.

  • 함수를 함수로써 호출한 경우 this 는 전역 객체를 참조한다. (메서드의 내부 함수도 동일)

  • 콜백 함수 내부에서의 this 는 해당 콜백 함수의 제어권을 넘겨받은 함수가 정의한 바에 따르며, 정의 하지 않은 경우는 전역 객체를 참조한다.

  • 생성자 함수에서의 this 는 생성될 인스턴스를 참조한다.

명시적 바인딩(Explicit binding)

  • call, apply 메서드는 this 를 명시적으로 지정하면서 함수 또는 메서드를 호출한다.

  • bind 메서드는 this 및 함수에 넘길 인수를 일부 지정해서 새로운 함수를 만든다.

  • 요소를 순회하면서 콜백 함수를 반복 호출하는 내용의 일부 메서드는 별도의 인자로 this 를 받기도 한다.


참고) 더글러스 크락포드가 생각한 this

더글러스 크락포드는 그의 저서 [How JavaScript Works]에서 this를 완전히 사용하지 않을 것을 권장한다. this가 없이 프로그램을 만드는 것은 생각보다 쉬우며, this를 언어에서 제거해도 언어는 여전히 튜링 완전(Turing Complete)하기 때문이다.

this는 대명사이다. 언어에 this가 있는 것만으로도, 그 언어에 대해서 말하는 것이 얼마나 힘들지 모른다.

Last updated