본문으로 바로가기

화살표 함수(Arrow function)

category JavaScript/ES6+ 2021. 2. 26. 18:41

이 글은 [인프런] Javascript ES6+ 제대로 알아보기 - 초급(정재남)를 토대로 작성되었음을 알려드립니다.


 

Arrow Function(화살표 함수)

다음의  ES5 의 함수 표현식과  es2015 의 화살표 함수를 비교해 보도록 하자.

ES5 js
var a = function () {
  return new Date()
};

var b = function (a) {
  return a * a
};

var c = function (a, b) {
  return a + b
};

var d = function (a, b) {
  console.log( a * b )
}

 

ES6(es2015) JS
let a = () => {
    return new Date()
};
let aa = () => new Date();

let b = (a) => {
    return a * a
};
let bb = a => a * a;

let c = (a, b) => {
    return a + b
};
let cc = (a, b) => a + b;

let d = (a, b) => {
    console.log( a * b )
};

기존의 함수 표현식에서  function 키워드를 삭제하고 인자를 받는 매개변수의  괄호()와  코드블록({}) 사이에  화살표(=>) 만 넣어주면
이것이 바로 화살표 함수(Arrow Function)입니다.

그리고 함수 내부의 내용이 반환값(return)만 있다면 코드블록(함수의 몸통)인  중괄호({})와  return 을 생략할 수 있습니다.
그리고 또 하나 생략할 수 있는 것이 인자가 하나만 받는다면 매개변수의 괄호()도 생략 가능하고, 여기서 주의해야 할 점은 인자가 없으면 생략할 수 없습니다.

 

js
let step1 = (x) => {
    return {
        x : x
    }
};

let step2 = (x) => ({
    x : x
});

let step3 = x => ({
    x : x
});

let step4 = x => ({ x });

위에서 함수 내부의 내용이 반환값(return)만 있다면  return 과 코드 블록인  중괄호({})를 생략할 수 있다고 언급했으나 객체 리터럴의 경우  return 과  코드블록({})을 생략하게 되면 남게 되는  코드블록({})의 경우는 화살표 함수의 함수 스코프로서 인식하게 되기 때문에 안의 내용의 위에서는  x: x 의 형태로 즉, 객체 리터럴을 표기하려고 한 것이므로 에러를 내게 됩니다.
그렇기 때문에  객체를 즉시 반환해야 하는 경우에는 예외적으로 객체임을 명시적으로 알려주기 위해서 ()를 묶어줌으로서 ()안의 값이 객체임을 표기해 주어야 합니다.

 

아래의 중접된 함수 형태에서 반환할 경우의 예제를 단계별로 좀더 살펴보도록 하겠습니다.

js
let fn01_step1 = function (a) {
    return function (b) {
        return a + b;
    }
};

let fn01_step2 = (a) => {
    return function (b) {
        return a + b;
    }
};

let fn01_step3 = a => function (b) {
    return a + b;

};

let fn01_step4 = a => (b) => {
    return a + b;
};

let fn01_step5 = a => b => a + b;

위  fn01_step1 코드를 줄여 나가면 최종적으로 아래와 같은 형태로 짧게 작성할 수 있습니다.

js
let finished = a => b => a + b;

 

 

Arrow Function's detail info

1)   (매개변수) => { 본문 }

2)   매개변수가 하나뿐인 경우 괄호 생략 가능

3)   매개변수가 없을 경우에는 괄호 필수

4)   본문이  return [식 or 값] 뿐인 경우  { } 와  return 키워드 생략 가능

5)   위 4) 에서  return 할 값이  객체인 경우네는 괄호 필수

js
const f = () => {
    a: 1,
    b: 2
};

const f = () => ({
    a: 1,
    b: 2
});

 

6)   실행 컨텍스트 생성시  this 바인딩을 하지 않음

화살표 함수는 기존 함수의 기능을 문법적으로 편하게 직관적으로 보여지기 위해 만들어진 이유보다 함수를 가볍게 하기 위해서 만들어진 목적이 더 큽니다. 함수를 실행하는 순간 함수의 덩어리에 대한 데이터를 모두 들고 컨텍스트를 생성하고 하는 등등의 내부에서 하는 일들이 많아서 무거웠으나 이렇게 기존에 무거웠음에도 무리없이 실행이 된 것은 무엇보다 컴퓨터의 성능이 좋아진 이유에 있습니다. 하지만 더이상 함수의 성능적 이슈를 방치할 수 없어서 좀더 가벼운 함수를 만들고자 화살표 함수가 나타나게 된 것입니다.
이렇게 함수의 성능을 높이기 위해 변화된 것 중의 하나가  this 바인딩 입니다.

js
const obj = {
    a : function () {
        console.log(this); // obj

        const b = function() {
            console.log( this ); // this 바인딩하는 객체가 window
        };
        b();
    }
};

obj.a();

const obj2 = {
    a : function () {
        console.log(this); // obj

        const b = () => {
            // this 를 바인딩하는 동작 자체를 하지 않음
            console.log( this ); // obj
        };
        b();
    }
};

obj2.a();

위 코드에서 확인해 보면 일반 함수에서 함수 자체가 실행되는 순간에  this 를 바인딩하는 작업을 하게 되는데 위 코드에서  this 를 찾는데 바인딩할 대상이 없으니 자동으로 전역객체를 바인딩하여  window 가 출력된 것이고  Arrow Function(화살표 함수) 은 실행 컨텍스트가 생성될 때  this 를 바인딩하는 작업 자체를 하지 않도록 설계되어 있어서 위 코드상에서는  this 를 찾으려고 하니  b의 실행 컨텍스트에 없으니 스코프 체이닝을 통해 외부 스코프에서  this 를 찾게 되어  obj 가 출력되는 것입니다.

즉, Arrow Function(화살표 함수)은 '함수 스코프'를 생성합니다. 다만, 실행 컨텍스트 생성시 this 를 바인딩하지 않습니다.

위 내용을 생각해 보면서 다음의 예제를 살펴보도록 하자.

js
const obj = {
    grades: [80, 90, 100],
    getTotal: function () {
        this.total = 0;
        this.grades.forEach(function(v) {
            this.total += v // this ???
        })
    }
};

obj.getTotal();
console.log(obj.total);

this.total,  this.grades 에서  this 는  obj 를 가리키고 있지만  forEach 메소드 내부의 콜백 함수(forEach 가 돌려주는 콜백함수)는 그냥 함수 실행이기 때문에 그 내부에서의  this 는  window 를 가리키게 되어  window.total 로 바인딩되게 됩니다.

js
var total = 0;
const obj = {
    grades: [80, 90, 100],
    getTotal: function () {
        this.total = 0;
        this.grades.forEach(function(v) {
            this.total += v
        })
    }
};

obj.getTotal();

console.log( total ); // 270

위 코드는 원하는 결과는 얻었지만 의도한 코드는 아니었을 것입니다.

즉, 콜백 함수에서의  this 가 달라졌기 때문에  this 바인딩을 해주어야 하는데 이는 다음과 같이 할 수 있습니다.

js
const obj = {
    grades: [80, 90, 100],
    getTotal: function () {
        this.total = 0;
        this.grades.forEach(function(v) {
            this.total += v
        }, this);
    }
};

obj.getTotal();
console.log( obj.total );

위와 같이  forEach 메소드에서 제공해 주고 있는 스펙에 따라  this 를 넘겨줄 수도 있지만 아래와 같이 화살표 함수를 사용할 수도 있습니다.

js
const obj = {
    grades: [80, 90, 100],
    getTotal: function () {
        this.total = 0;
        this.grades.forEach( v => {
            this.total += v
        });
    }
};

obj.getTotal();
console.log( obj.total );

 

7)   6) 에서 화살표 함수는 this 바인딩이 안된다고 언급했는데 명시적인 this 바인딩은 어떻게 될까?

결론부터 말하자면 명시적인 this 바인딩을 되지 않습니다. 즉, this 바인딩이 안됩니다.

js
const a = () => {
    console.log( this );
};

a(); // window
a.call( {} ); // window

명시적인  this 바인딩을 사용하는 방법 중에 하나가  call 을 이용하는데  call 메소드의 첫 번째 인자는 명시적으로 넘겨줄  this 를 바인딩할 대상을 넘겨줍니다.  화살표 함수를 사용할 경우 위와 같이  this{} 객체를 넘겨주더라도  this 는 여전히  window 를 가리키고 있는 것을 확인할 수 있을 것입니다.

그렇다면 일반 함수의 경우에는 어떻게 되는지 다음의 코드를 통해 확인해 보도록 합니다.

js
const a = function() {
    console.log( this );
};

a(); // window
a.call( {} ); // {} 빈 객체

a.call( {} );a 함수를 실행할 때 this 를 {} 요놈인 채로 c 함수를 실행하라.. 라고 했으니  this 가 빈 객체를 가리키게 됩니다.
즉, 일반적인 함수(콜백함수 포함)에서는  this 를 바인딩을 하지만 Arrow Function(화살표함수) 에서는  this 를 바인딩하지 않는다는 것입니다.

 

화살표 함수에서 call, apply 의 기능은 수행하는가?

call,  apply 메소드는 본연의 기능은 수행하지만 제약이 있다는 점만 주의하면 됩니다.
즉, this 만 바인딩되지 않는다는 것입니다.

js
// 기존 ES5 함수 선언문
function sum(...arg) {
    console.log( this );
    return arg.reduce( (p,c) => p+ c);
}

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


// 화살표 함수를 사용한 경우의 this
const sum2 = (...arg) => {
    console.log( this );
    return arg.reduce( (p,c) => p+ c);
};

console.log( sum2( 1, 2, 3, 4, 5 ) ); // 15
sum2.call( {}, 1, 2, 3, 4, 5 ); // 15

위 코드를 확인해 보면  this 만 다를 뿐  call,  apply 는 인자들을 넘겨주는 것에 대한 그 기능은 충실히 수행하고
다만,  this 만 바인딩되지 않을 뿐입니다.

 

js
const a = (...rest) => {
  console.log(this, rest);
}
a.call({a: 1}, 1, 2, 3);
a.apply([], [4, 5, 6]);
const b = a.bind(null, 7, 8, 9, 10);
b()
js
const obj = {
  f() {
    const a = (...rest) => {
      console.log(this, rest)
    };
    a.call({a: 1}, 1, 2, 3);
    a.apply([], [4, 5, 6]);
    const b = a.bind(null, 7, 8, 9, 10);
    b();
  }
};
obj.f()

 

8)   화살표 함수는 생성자함수로 사용할 수 있을까?

js
// ES5 함수 선언문
function sum(...arg) {
    console.log( this );
    return arg.reduce( (p,c) => p+ c);
}

// Arrow Function
const sum2 = (...arg) => {
    console.log( this );
    return arg.reduce( (p,c) => p+ c);
};

console.dir( sum );
console.dir( sum2 );

개발자 도구에서  sum 을 확인해 보면  prototype 이 있는 것을 볼 수 있고 이는 생성자 함수로 사용할 수 있다는 것을 의미합니다.
하지만,  sum2 는  prototype 이 없는 것을 확인할 수 있고 이는 생성자 함수로 사용할 수 없다는 것을 말합니다.

다음의 코드를 통해 개발자 도구에서 확인해 보자.

js
// ES5 함수 선언문
function sum() {
    console.log( this );
}

// Arrow Function
const sum2 = () => {
    console.log( this );
};

const b = new sum();

const c = new sum2(); // sum2 is not a constructor

정리하면,  Arrow Function(화살표 함수) 과  concise method(메소드 축약형) 은 prototype 프로퍼티가 없으며 이는 생성자 함수로 사용할 수 없다는 것을 의미합니다.

아래 예제를 통해 차이점을 살펴보자.

js
 const b = {
    title: '자바스크립트',
    // ES6 메소드 형태
    bb () {
        return this.title;
    },
    // ES6 메소드이지만 화살표 함수를 사용한 경우
    a : x => {
        return this.title;
    }
};

console.log( b.bb() );
console.log( b.a() ); // this 바인딩을 하지 않아서 undefined 출력

window.title = 'ES6';
console.log( b.a() );

a 의 값이 화살표 함수일 경우 메소드처럼 호출할 수는 있으나  this 가 바인딩되지 않는 점을 보면 메소드의 역할보다는 함수로서의 기능을 한다는 것을 알 수 있습니다.

다시 말해, 객체에서  this 를 사용하는 것은 자신의 객체에 다른 프로퍼티를 바라보게 하기 위해 사용하는 목적이 있는데 그렇기 하려면 메소드 축약형이나 기존 방식을 사용하면 될 것이고, this 를 바라보게 할 상황이 아니라면 화살표 함수를 사용할 수 있습니다.
정리하면, 메소드를 사용하려면 메소드 축약형, 기존 방식을 그렇지 않다면 화살표 함수를 사용하면 될 것이고, 화살표 함수는 메소드 안에서 같은 this 를 바라보게 할 때 즉, 내부 함수로서 사용하는 경우에 의의가 있다고 할 수 있습니다.

js
 const b = {
    title: '자바스크립트',
    bb () {
        const c = x => {
            return this.title;
        };
        console.log( c() );
    },
};

b.bb();

 

9)   이외의 기타 사항

this 외에도  super,  arguments,  new.target 등을 바인딩하지 않는다.

 

 

Jaehee's WebClub