JavaScript ES6 Arrow Function

Javascript ES6 Arrow Function

ES6

1. Syntax

  • Arrow function(화살표 함수)은 function 키워드 대신 화살표(=>)를 사용하여 간략한 방법으로 함수를 선언할 수 있다. 하지만 모든 경우 사용할 수 있는 것은 아니다. 문법은 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 매개변수 지정 방법
() => { ... } // 매개변수가 없을 경우 ES6
function() { ... } // ES5

x => { ... } // 매개변수가 한개인 경우, 소괄호를 생략할 수 있다.
(x, y) => { ... } // 매개변수가 여러개인 경우

// 함수 몸체 지정 방법
x => { return x * x } // single line block
x => x * x // 함수 몸체가 한줄의 표현식이라면 중괄호를 생략할 수 있으며 자동으로 return된다. 위 표현과 동일하다.

() => { return { a: 1 }; }
() => ({ a: 1 }) // 위 표현과 동일하다. 객체 반환시 소괄호를 사용한다.

() => { // multi line block.
const x = 10;
return x * x;
};

body가 한 줄일때만 return을 생략할 수 있다.

2. Arrow function의 호출

  • Arrow function은 익명 함수로만 사용할 수 있다. 따라서 Arrow function을 호출하기 위해서는 함수표현식을 사용한다.
1
2
3
4
5
6
7
// ES5
var pow = function (x) { return x * x; };
console.log(pow(10)); // 100

// ES6
const pow = x => x * x;
console.log(pow(10)); // 100

일급객체의 대해서 알아보자.

  • 또는 콜백함수로 사용할 수 있다. 이 경우 일반적인 함수 표현식보다 표현이 간결하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// ES5
var arr = [1, 2, 3];
var pow = arr.map(function (x) { // x는 요소값
return x * x;
});

console.log(pow); // [ 1, 4, 9 ]

// ES6
const arr = [1, 2, 3];
const pow = arr.map(x => x * x);

console.log(pow); // [ 1, 4, 9 ]

map은 순회를 하며 1번째 인자는 요소값 2번째 인자는 index를 받는다.

3. arguments와 rest 파라미터

  • arguments 객체는 함수 호출 시 전달된 인수(argument)들의 정보를 담고 있는 순회가능한(iterable) 유사 배열 객체(array-like object)이다.
  • 함수 객체의 arguments 프로퍼티는 arguments 객체를 값으로 가지며 함수 내부에서 지역변수처럼 사용된다.
1
2
3
4
5
6
// ES5
var foo = function () {
console.log(arguments);
};

foo(1, 2); // { '0': 1, '1': 2 }
  • ES5에서 매개변수 갯수가 확정되지 않은 가변 인자 함수를 구현할 때 arguments 객체가 유용하게 사용된다.

  • 파라미터를 통해 인수를 전달받는 것이 불가능하므로 arguments 객체를 활용하여 인수를 전달받는다. 하지만 arguments 객체는 유사 배열 객체이기 때문에 배열 메소드를 사용하려면 Function.prototype.call, Function.prototype.apply를 사용하여야 하는 번거로움이 있다.

1
2
3
4
5
6
7
8
9
10
11
// ES5
function sum() {
// 가변 인자 함수의 경우, 파라미터를 통해 인수를 전달받는 것이 불가능하므로 arguments 객체를 활용하여 인수를 전달받는다.
// arguments 객체를 배열로 변환
var array = Array.prototype.slice.call(arguments);
return array.reduce(function (pre, cur) {
return pre + cur;
});
}

console.log(sum(1, 2, 3, 4, 5)); // 15
  • ES6의 Arrow function에는 함수 객체의 arguments 프로퍼티가 없다.
1
2
3
4
5
var es5 = function () {};
console.log(es5.hasOwnProperty('arguments')); // true

const es6 = () => {};
console.log(es6.hasOwnProperty('arguments')); // false
  • ES6에서는 rest 파라미터를 사용하여 가변인자를 함수 내부에 배열로 전달할 수 있다. arguments 프로퍼티가 없는 Arrow function에서 가변 인자 함수를 구현하는 경우, rest 파라미터를 사용하여야 한다.
1
2
3
4
5
6
7
8
9
// ES6
const sum = (...args) => {
// console.log(arguments); // Uncaught ReferenceError: arguments is not defined
console.log(Array.isArray(args)); // true

return args.reduce((pre, cur) => pre + cur);
};

console.log(sum(1, 2, 3, 4, 5)); // 15

4.this

  • function 키워드를 사용하여 생성한 일반 함수와 Arrow function와의 가장 큰 차이점은 this이다.

4.1 일반 함수의 this

  • 일반 함수의 경우, 해당 함수 호출 패턴에 따라 this에 바인딩되는 객체가 달라진다. 콜백함수 내부의 this는 전역 객체 window를 가리킨다.
1
2
3
4
5
6
7
8
9
10
11
12
13
function Prefixer(prefix) {
this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
// (A)
return arr.map(function (x) {
return this.prefix + ' ' + x; // (B)
});
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));

생성자함수는 1. 빈객체를 생성하고 this를 담을 곳이다, 2.

  • (A)에서의 this는 생성자 함수 Prefixer가 생성한 객체, 즉 생성자 함수의 인스턴스(위 예제의 경우 pre)이다.

  • (B)에서 사용한 this는 아마도 생성자 함수 Prefixer가 생성한 객체(위 예제의 경우 pre)일 것으로 기대하였겠지만 이곳에서 this는 전역 객체 window를 가리킨다. 이는 생성자 함수와 객체의 메소드를 제외한 모든 함수(내부함수, 콜백함수 포함)의 내부의 this는 전역객체를 가리키기 때문이다.

  • 해결방법 4가지를 아래에 소개하겠다.

  • 1.that변수를 함수안에 선언해준다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // Solution 1: that = this
    function Prefixer(prefix) {
    this.prefix = prefix;
    }

    Prefixer.prototype.prefixArray = function (arr) {
    var that = this; // this: Prefixer 생성자 함수의 인스턴스
    return arr.map(function (x) {
    return that.prefix + ' ' + x;
    });
    };

    var pre = new Prefixer('Hi');
    console.log(pre.prefixArray(['Lee', 'Kim']));
  • 2.map의 2번째 parameter로 this를 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Solution 2: map(func, this)
function Prefixer(prefix) {
this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + ' ' + x;
}, this); // this: Prefixer 생성자 함수의 인스턴스
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));
  • ES5에 추가된 Function.prototype.bind()로 this를 바인딩한다. call(), apply()도 사용 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// Solution 3: bind(this)
function Prefixer(prefix) {
this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
return arr.map(function (x) {
return this.prefix + ' ' + x;;
}.bind(this)); // this: Prefixer 생성자 함수의 인스턴스
};

var pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));

4.2 Arrow function의 this

  • Arrow function은 언제나 자신을 포함하는 외부 scope에서 this를 계승 받는다.
  • Lexical this란? Arrow function은 자신만의 this를 생성하지 않고 자신을 포함하고 있는 컨텍스트로 부터 this를 계승 받는다.
1
2
3
4
5
6
7
8
9
10
function Prefixer(prefix) {
this.prefix = prefix;
}

Prefixer.prototype.prefixArray = function (arr) {
return arr.map(x => `${this.prefix} ${x}`);
};

const pre = new Prefixer('Hi');
console.log(pre.prefixArray(['Lee', 'Kim']));
  • ``를 사용하여서 한번에 문자열로 추가하였다.

5. Arrow Function을 사용해서는 안되는 경우

  • Arrow Function는 Lexical this를 지원하므로 콜백함수에 사용하기 편리하다. 하지만 Arrow Function을 사용하는 것이 오히려 혼란을 불러오는 경우도 있기 때문에 주의하여야 한다.

5.1 메소드

  • 메소드 정의 시 Arrow Function을 사용하는 것은 피해야 한다. Arrow Function으로 메소드를 정의하여 보자.
1
2
3
4
5
6
7
// Bad
const obj = {
name: 'Lee',
sayHi: () => console.log(`Hi ${this.name}`)
};

obj.sayHi(); // Hi undefined
  • 해당 메소드를 소유한 객체 즉 해당 메소드를 호출한 객체에 this를 바인딩하지 않고 전역 객체에 바인딩된다. 따라서 Arrow Function으로 메소드를 정의하는 것은 바람직하지 않다.

  • ES6의 축약 메소드 표현 방식으로 위 예제를 수정하여 보자. ES6의 축약 메소드 표현은 메소드명에 할당된 함수를 위한 단축 표기법이다.

1
2
3
4
5
6
7
8
9
// Good
const obj = {
name: 'Lee',
sayHi() { // === sayHi: function() {
console.log(`Hi ${this.name}`);
}
};

obj.sayHi(); // Hi Lee

5.2 prototype

  • prototype에 메소드를 할당하는 경우도 동일한 문제가 발생한다. prototype에 Arrow Function 메소드를 할당하여 보자.
1
2
3
4
5
6
7
8
// Bad
const obj = {
name: 'Lee',
};

Object.prototype.sayHi = () => console.log(`Hi ${this.name}`);

obj.sayHi(); // Hi undefined
  • Arrow Function으로 객체 메소드를 정의하였을 때와 같은 문제가 발생한다. 일반 함수를 할당한다.
1
2
3
4
5
6
7
8
9
10
// Good
const obj = {
name: 'Lee',
};

Object.prototype.sayHi = function() {
console.log(`Hi ${this.name}`);
};

obj.sayHi(); // Hi Lee

5.3 생성자 함수

  • Arrow Function은 생성자 함수로 사용할 수 없다. Arrow Function은 prototype 프로퍼티를 가지고 있지 않기 때문이다.
1
2
3
4
const Foo = () => {};
// Arrow Function은 prototype 프로퍼티가 없다
console.log(Foo.hasOwnProperty('prototype')); // false
const foo = new Foo(); // TypeError: Foo is not a constructor

arrow function은 callback에서 주로사용한다.

Share