상세 컨텐츠

본문 제목

[JavaScript] Arrow function과 this 키워드

Log.Develop/JS&TS

by bluayer 2020. 4. 28. 14:38

본문

서론

ES6에는 다양한 내용들이 새로 포함되었고, 유용한 것들이 많다.

그렇지만 사용 빈도로 순위를 매긴다면,

필자는 오늘 다룰 Arrow function이 다섯 손가락 안에 꼽힐 것이라고 확신한다.

 

또한, Arrow function을 그냥 단순하게 축약 문법이라고 생각하고 쓰기엔

생각보다 신경 써서 알아 둬야 할 부분들이 있다.

(이 내용이 바로 this와 관련된 부분이다.)

 

아무쪼록, Arrow function에 대해 간단히 알아보자.

또한 this 키워드에 대해서도 간략히 알아볼 것이다.

(솔직히 MDN 문서에 아주 잘 설명되어 있지만, 간단한 정리는 언제나 좋다.)

 

** 참고로 대부분 코드의 실행 결과를 접은 글로 첨부하였다.
** 실행 결과가 궁금한 분들은 참고하시길 바란다.

 

Arrow function이란 무엇인가?

ES6에서 새로 만든 arrow notation으로 작성된 함수라고 할 수 있다.

function 키워드와 중괄호를 줄일 수 있는 단축 문법이다.

이런 단축 문법들이 있는데,

 

  1. function을 생략해도 된다.
  2. 함수에 매개변수가 단 하나 뿐이라면 괄호도 생략할 수 있다.
    (그러나 0개거나 2개 이상이라면 써야 한다!!!)
  3. 함수 바디가 표현식 하나라면 중괄호와 return 문을 생략할 수 있다.

보통 이렇게 쓴다.

 

// 1번
() => { return "hi" };

// 2번
a => { return a };

// 3번
() => "hi";

 

Arrow function, 어떨 때 쓰면 좋을까?

필자가 자주 쓰는 상황은 다음과 같다.

 

  • 익명 함수를 써야 할 때 (callback 함수를 작성할 때, 변수에 함수를 선언할 때)
  • 직관적으로 작성하고 싶을 때 (보통 위의 단축 문법 2번과 3번에 해당한다)
  • 객체의,
    중첩된 함수의,
    내부 함수에서 this 키워드를 바인딩하기 싫을 때

3번째 상황은 설명이 필요할 것이기 때문에

먼저 this 키워드에 대해 아래에서 알아보도록 하자.

 

this 키워드

this 키워드는 자바와 같은 객체 지향 언어를 다뤄본 독자라면 익숙할 것이다.

(python에서는 self다!)

 

this를 간략히 표현하자면, 함수에서는 이런거다.

 

호출한 메서드를 소유하는 객체

 

코드로 한 번 사용 방법을 보자.

 

const o = {
  name: "Jungwoo",
  speak() { return `My name is ${this.name}`; }
}

o.speak(); // "My name is Jungwoo"
더보기

코드 실행 결과

코드 실행 결과

 

생각보다 간단하지 않은가?

그런데, 한 가지 중요한 점이 있다.

 

this는 함수를 '어디서 선언했느냐'가 아니라,

'어디서 호출했는가'에 따라 달라지게 된다.

 

위와 이어지는 아래 예시를 하나 보자.

 

const speak = o.speak;
speak === o.speak; // true
speak(); // "My name is " 혹은 "My name is undefined"
더보기

코드 실행 결과

코드 실행 결과

 

놀랍게도 같은 함수를 가리키고 있지만, this는 제대로 표현되지 않는다.

 

즉 this라는 키워드는 호출된 함수가

어디서 호출되었는지에 따라,

무엇과 결합(바인딩)될지도 달라지게 된다.

 

특히 객체의 함수의 중첩 함수에서 this를 사용하게 되면 엉망진창이 되어 버리는 걸 볼 수 있다.

 

const o = {
  name: 'Bluayer',
  speak: function () {
    function getName() {
      return `${this.name}`;
    }
    return `My name is ${getName()}`;
  }
};

o.speak(); // "My name is undefined" 혹은 "My name is " 출력
더보기

코드 실행 결과

코드 실행 결과

 

객체 o의 speak 함수 내부의 getName에선 this를 어떤 것으로 생각하고 있을까?

보통 undefined 혹은 전역 객체(브라우저라면 window와 같은 것)으로 생각하게 된다.

 

그래서 예전 코드들에서는,

이런 문제를 해결하기 위해 이렇게 되어 있다.

 

const o = {
  name: 'Bluayer',
  speak: function () {
    // 여기까지는 this에 객체 o가 잘 결합된다.
    // 따라서 self를 선언하고 여기에 결합시킨다.
    const self = this;
    function getName() {
      return `${self.name}`;
    }
    return `My name is ${getName()}`;
  }
};

o.speak(); // "My name is Bluayer"
더보기
코드 실행 결과
코드 실행 결과

 

객체 o에서 호출된 speak에 할당된 익명 함수에서는 당연히 this에 객체 o가 잘 결합하지 않겠는가?

(당연히 호출한 곳이 객체 o기 때문이다!!)

 

그러나, 내부의 getName에서는 this를 그대로 쓰면 잘 작동하지 않는다.

따라서 미리 self라는 변수에 할당을 한 다음에 넘겨주는 것이다!!

계속 함수를 중첩하더라도 우리가 의도한 것처럼 this를 잘 쓸 수 있게 되는 것이다.

 

그런데, arrow function을 사용하면

위처럼 self를 선언하고, 이를 결합시키는 과정이 필요 없어진다.

 

Arrow function으로 해결하자!

어떻게 바뀌었는지 코드부터 보자.

 

const o = {
  name: 'Bluayer',
  speak: function () {
    getName = () => {
      return `${this.name}`;
    }
    return `My name is ${getName()}`;
  }
};

o.speak(); // "My name is Bluayer"
더보기

코드 실행 결과

코드 실행 결과

 

매우 깔끔한 코드가 되었다.

arrow function은 this를 다른 변수와 마찬가지로 정적(lexically)으로 결합시키기 때문

가능한 일이다.

 

쉽게 설명하면,

this를 동적으로 막 결합시키는게 아니라,

맥락 상 위의 코드부터 보고 결합한다고 생각하면 된다.

 

지금은 이름만 반환하는 단순한 코드지만,

만약 이름을 조작해야 한다거나 하는 복잡한 과정이 있다면

훨씬 유용하고 직관적으로 this를 사용할 수 있을 것이다.

 

Method로 사용하는 Arrow function?

결론 : 화살표 함수는 메소드로 사용할 수 없다.

(여기서 메소드란, 객체의 프로퍼티로 있는 함수를 의미한다.)

 

*프로퍼티?

속성과 같은 뜻이라고 생각하면 편하다.
우리가 객체에 있는 함수를 부를 때 '객체이름.함수이름()' 이런 식으로 호출하지 않는가?
저기서  우리가 호출한 함수가 프로퍼티의 일종이며 메소드라고 부른다.

 

우리는 메소드 내부의 중첩된 함수에서 계속 Arrow function 을 써왔다.

그렇다면 바로 그냥 arrow function을 쓰는 경우는 없을까?

그렇다, 그렇게 쓰면 안된다.

 

그러니깐, 이런 게 안 된다는 거다.

 

const obj = {
  a: 'hi',
  b: () => console.log(this.a, this),
  c: function () {
    console.log(this.a, this);
  }
}

obj.b(); // prints undefined, Window 혹은 전역객체
obj.c(); // prints 'hi', Object {...}
더보기

코드 실행 결과

코드 실행 결과

 

근데 또 이렇게 쓰는 건 당연히 된다.

(이유는, 메소드로 직접 쓴 것이 아니기 때문이다!!)

 

const obj = {
  a: 'hi',
  b: function () {
    const hi = () => console.log(this.a, this);
    hi();
  },
  c: function () {
    console.log(this.a, this);
  }
}

obj.b(); // prints 'hi', Object {...}
obj.c(); // prints 'hi', Object {...}
더보기

코드 실행 결과

코드 실행 결과

 

혹시 위의 코드에서 변수를 선언하기 귀찮을 수 있다. 충분히 이해한다.

그럴 땐, IIFE 형식으로 반환하는 방법도 있다.

 

const obj = {
  a: 'hi',
  b: function () {
    return (() => console.log(this.a, this))();
  },
  c: function () {
    console.log(this.a, this);
  }
}

obj.b(); // prints 'hi', Object {...}
obj.c(); // prints 'hi', Object {...}

 

그 외 알아두면 좋은 것들

  • new 키워드와 같이 사용될 수 없다.
  • prototype 속성이 없다.
  • yield 키워드를 사용할 수 없다.
    → 즉, 제너레이터로 화살표 함수를 사용할 수 없다.
    (But, 내부의 중첩된 함수에서는 사용될 수 있음)

 

참고 자료

1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/%EC%95%A0%EB%A1%9C%EC%9A%B0_%ED%8E%91%EC%85%98

 

화살표 함수

화살표 함수 표현(arrow function expression)은 function 표현에 비해 구문이 짧고  자신의 this, arguments, super 또는 new.target을 바인딩 하지 않습니다. 화살표 함수는 항상 익명입니다. 이  함수 표현은 메소드 함수가 아닌 곳에 가장 적합합니다. 그래서 생성자로서 사용할 수 없습니다.

developer.mozilla.org

2. Learning JavaScript(코뿔소 책), 이선 브라운, O'REILLY

 

* 최대한 꼼꼼하게 보며 썼지만, 혹시 틀린 부분이 있다면 적극적인 피드백을 부탁드립니다!

관련글 더보기

댓글 영역