Javascript/ES6

ES6(1) - Arrows

hun.a 2017. 8. 31. 11:12
반응형
예전부터 ES6에 대해 정리를 한번 해보려고 했는데 계속 미루기만 하다가 드디어! 포스팅을 하기로 마음을 먹었다.

ES6는 무엇인가?!

ECMAScript 6 (이하 ES6)은 ES5가 2009년에 발표된 이후 2015년인 6년만에 발표된 새로운 Javascript 언어 스펙이다.

따라서 굉장히 많고, 굉장히 유용하고, 굉장히 신기한(?) 기능들이 대거 추가되었다.

그중에서 첫 번째로 Arrow function 이라고 불리는 화살표 함수에 대해 알아보려고 한다.

해당 내용은 MDN web docs의 화살표 함수 항목을 참고했습니다.


정의


간단하게 설명하면, 기존 function 표현에 비해 구문이 짧고, 항상 익명으로 사용되며, this, arguments, super, new.target을 바인딩 하지 않는다 이다. 

중요한 점은, 화살표 함수는 기존의 함수를 대체하는 것이 아니다! 따라서 this 가 굉장히 다르게 동작한다.


이것으로 Arrow 에 대한 내용은 모두 마치고...



이러면 따귀를 맞을 수 있으니 하나씩 살펴보도록 하겠다.



구문


기본 구문은 아래와 같다.

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
          // 다음과 동일함:  => { return expression; }

// 매개변수가 하나뿐인 경우 괄호는 선택사항:
(singleParam) => { statements }
singleParam => { statements }

// 매개변수가 없는 함수는 괄호가 필요:
() => { statements }


기존 function() { ... } 구문이 () => { ... } 와 같은 형태로 바뀌었다고 보면 간단하다.

하지만 여기서 다가 아니다. 좀 더 고급진 구문을 살펴보자.

// 객체 리터럴 식을 반환하는 본문(body)을 괄호 속에 넣음:
params => ({foo: bar})

// 나머지 매개변수 및 기본 매개변수가 지원됨
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => { statements }

// 매개변수 목록 내 비구조화도 지원됨
var f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f();  // 6


모르는게 갑자기 너무 많이 나왔다. 나머지 매개변수? 기본 매개변수? 비구조화 ?? 앞으로 포스팅 해 나갈 것이니 너무 염려 말길 바란다.

자 그럼, 하나씩 자세히 살펴보도록 하자.


짧은 함수


짧은 함수는 화살표 함수 도입에 큰 영향을 준 2가지 요소 중 하나이다. (다른 하나는 바로 아래에)

짧은 함수가 무엇이냐? 바로 이런 것이다. 익명 함수라고 보는게 맞겠다.
var a = function() {
  console.log("I'm a!");
}

바인딩 되지 않는 this


또 다른 화살표 함수에 영향을 준 놈은 바로 이것이다.

기존의 모든 함수들은 자신만의 this를 바인딩 하였다.
생성자에서는 새로 생성될 객체를, 메소드에서는 호출된 객체를, 함수에서는 전역 객체를...

(왜 이렇게 했을까? 헷갈리게)

객체지향에 길들여진 우리(?)는 this가 무조건 객체를 가리킬 것이라고 상상을 했을 것이다.

하지만... javascript는 그렇게 호락호락하게 this를 바인딩 해주지 않았다...
function Person() {
  this.name = 'human';
  this.func = function() {
    console.log(this.name);
  };
  this.funcInFunc = function() {
    (function() {
      console.log(this.name);
    })();
  };
}

var a = new Person();
a.func();
a.funcInFunc();

Person 객체의 funcInFunc() 메소드에서 내부적으로 익명 함수를 사용해서 this.name을 출력하고 있다.

func(), funcInFunc() 수행 결과 예상대로라면 'human' 이 두 번 출력되야 하는데 결과는...?!
human
undefined

처음 Javascript를 접하는 사람이라면 굉장한 혼돈의 카오스에 빠져 고민하다 머리털 다 빠질지도 모른다.

이유는 위에서 설명한 함수에서는 전역 객체를... 때문이다.

자, 그럼 이번에는 화살표 함수를 이용해서 수행해보자.

function Person() {
  this.name = 'human';
  this.func = function() {
    console.log(this.name);
  };
  this.funcInFunc = function() {
    (() => {
      console.log(this.name);
    })();
  };
}

var a = new Person();
a.func();
a.funcInFunc();

과연 그 결과는...!!! 짜잔!

human
human

우리의 예상대로 'human'이 두 번 출력이 되었다.

왜 그럴까? 익명 함수와 화살표 함수의 무슨 차이 때문에 그럴까?

바로, 화살표 함수의 특징이 자신만의 this를 생성하지 않고, 자신을 감싸고 있는 컨텍스로부터의 this를 가져오기 때문이다. 참 쉽죠? 

또한 이러한 this는 실행 단계까 아닌 선언 단계에서 설정된다. (lexical, 정적 이라고 한다!)

반면, 일반 함수의 this 는 lexical이 아닌 dynamic this 이다.



use strict 와의 관계


this 는 lexical 이므로 use strict (엄격 모드) 규칙이 무시가 된다. (왜?)

즉, 화살표 함수 내부에서 this를 사용하게 되면 선언과 동시에 상위 컨텍스트의 this로 설정이 된다. 

이후에는 무슨짓을 해도 이 this를 바꿀 수 없다.


아래 예제를 보자.

'use strict';
var func = function() {
  return this;
};

var arrow = () => {
  return this;
};

console.log('func()', func());
console.log('arrow()', arrow());


엄격 모드를 사용했으므로 두 함수의 결과가 모두 undefined가 나올 것이라고 생각 했지만... 전혀 그렇지 않다. 

func() undefined
arrow() {}


자 그럼 분석을 해보자. 왜 이런 결과가 나왔을까?

우리가 알고 있는 모든 지식을 모조리 뇌로 로딩해서 조합해보자.


먼저, 화살표 함수의 this는 방금 바로 위에서 이야기 한대로 선언 시점에 상위 컨텍스트의 this로 결정이 된다.

또한 그 후로는 무슨짓을 해도 this를 바꿀 수 없다고 했다.

그렇다면 엄격 모드를 사용해도 실행 시점에 this의 값을 다른 값으로 변경할 수 없다는 말이 된다.


그렇다면 func() 함수는 어떨까?

일반 함수의 경우 this는 lexical this가 아니라 dynamic this 이다.

따라서 일반 함수는 this가 변할 수 있으므로 엄격 모드로 인해 전역 객체가 아닌 undefined 로 변경된다.

javascript의 this... 이놈시키...




call 또는 apply를 통한 피호출


화살표 함수는 call, apply, bind를 통해 다른 객체로 this를 변경할 수 없다 고 바로 위에서 이야기 했다.

그럼 진짠지 한번 확인을 해보자.

var outer = {
  base: 100
};

var obj = {
  base: 1,
  func: function(a) {
    var f = function(v) {
      return this.base + v;
    };

    return f.call(outer, 1);
  },
  arrow: function(a) {
    var f = v => this.base + v;

    return f.call(outer, 1);
  }
};

console.log(obj.func(1));
console.log(obj.arrow(1));

obj 메소드 func와 arrow는 각각 내부 함수와 화살표 함수를 통해 this.base와 파라미터로 들어온 값 v를 더한 후 outer 객체로 바인딩하여 결과값을 리턴한다.

먼저 내부 함수의 경우 this는 전역 객체를 가리킬 것이고, 화살표 함수는 바로 위의 컨텍스트인 obj를 가리킬 것이다.

자, 그럼 수행 결과를 살펴보자. 

101
2

짠! func()outer 객체로 바인딩되서 결과값이 101이 되었고,

arrow()this는 여전히 obj를 가리키므로 2가 출력된다.

즉, 실행 시점에서 화살표 함수의 this는 다른 객체로 바인딩이 되지 않는다.




바인딩 되지 않는 arguments


저어어어어기 위에서 이야기 했듯이 화살표 함수는 arguments를 바인드 하지 않는다.

즉, 화살표 함수 내 arguments는 그저 바깥 컨텍스트의 arguments라는 같은 이름을 가리킬 뿐이다.

아래 예제를 한번 보자.

var arrow = () => {
  for (arg of arguments) {
    console.log('arrow: ', arg);
  }
};

var func = function() {
  for (arg of arguments) {
    console.log('func: ', arg);
  }
};

arrow(1, 2, 3);
func(1, 2, 3);

위 예제를 수행해 보면... arrow(1, 2, 3)의 결과는 이상한 값들이 나오거나, 에러가 날 것이다.

반면, func(1, 2, 3)은 정상적으로 1, 2, 3 이 이쁘게 출력 된다.


메소드로 사용되는 화살표 함수


다시, 화살표 함수의 this는 상위 컨텍스트의 this로 선언 시점에 결정이 된다고 하였다.

그렇다면 객체 리터럴에서 메소드를 정의 할 때 화살표 함수를 사용한다면 어떻게 될까? 물론 되긴 하지만 this가 맛탱이 가게 된다.
var obj = {
  name: 'human',
  sayName: () => {
    console.log(this.name, this);
  }
};

obj.sayName();

자, 여기에서 sayName()으로 선언된 화살표 함수의 상위 컨텍스트는 무엇일까?

obj? 아니다. 컨텍스트로 함수를 가리키는데 obj는 객체이다. 따라서 바로 상위 컨텍스트인 전역 객체를 this로 설정한다.

어딜가나 javascript는 this가 문제구나...


new 연산자 사용


저어어어어기 위에서 말했지만 생성자로써 화살표 함수를 사용할 수 있다? 없다?

다음 예제를 보자.
var obj = () => {};

new obj();

수행하면 바로 에러가 떨어진다.


yield 키워드 사용


이게 뭐지? 아직 ES6를 다 확인하지 못했다.

하지만, 화살표 함수에서 yield 키워드는 사용할 수 없다. ^^



반응형