화살표 함수, 뭐가 다를까?

terry yoon·2021년 9월 27일
0
post-thumbnail

화살표 함수는 ES6 이후 도입되어 함수를 간편하게 정의할 수 있는 방법이다. 일반적으로 함수를 정의하는 방법이 간단하다는 것만 알고 있지만, 실제로 화살표 함수의 내부 동작 원리 역시 일반 함수 정의 방법보다 간소화 되어 있다.

따라서 화살표 함수에 대한 이해를 통해 화살표 함수를 언제 어떻게 사용하는지, 화살표 함수의 장단점은 무엇인지 알아보는 것은 좋은 코드를 작성하는데 좋은 한 걸음을 제시할 것이라고 생각한다.

화살표 함수, 왜 도입되었을까?

화살표 함수는 ES6에서 많은 문법들과 함께 새로운 함수 정의 방법으로 소개되었다. 화살표 함수가 생긴 이유를 알기 전 화살표 함수가 생기기 전의 함수에 대해 알 필요가 있다.

화살표 함수가 생기기 이전에 모든 함수는 일반 함수 뿐만 아니라 생성자 함수로 호출할 수 있었다. 즉, 함수를 정의하는 방법부터 함수 객체를 호출하는 방법에 있어 생성자 함수와 일반 함수의 명확한 구분과 제약이 없었다.

물론 생성자 함수는 new 연산자와 함께 호출해야 생성자 함수로 동작하지만, new 연산자가 없으면 자동으로 일반 함수로 호출하게 된다. 이는 유연한 문법을 제공한다는 장점이 있지만, 반대로 사용자가 실수할 가능성을 높인다는 단점이 있다.

따라서 ES6 이후부터 함수의 목적에 맞는 함수 선언 방법을 3가지의 종류로 구분하였다.

1. 일반 함수 

- 생성자 함수로 호출할 수 있고, 프로토타입 체인을 구성 (prototype, constructor) 
- 전달한 인수를 함수 내에서 참조 가능(arguments) 

2. 메서드 

- 생성자 함수로 호출 불가능 
- 상속을 통해 수퍼 클래스 호출 가능(super 키워드) 
  (참고 : 메서드 자신이 포함된 프로토타입을 가르키고 있는 [[HomeObject]] 내부 메서드를 통해 상위 프로토타입 참조 가능)

3. 화살표 함수 

- 생성자 함수 호출 불가능
- 상속 불가능 

화살표 함수 (1) - 정의 방법

다음은 화살표 함수를 정의하는 방법과 주의사항이다.

  • 화살표 함수는 함수 표현식으로 정의해야만 한다. (함수 선언문 불가)

    const multiply = (x, y) => x * y;
    • 화살표 함수는 함수 표현식으로만 정의해야 한다. 함수 표현식이란 함수 리터럴을 식별자에 할당하여 함수를 정의하는 방법을 말한다. 화살표 함수 역시 함수 객체로 평가될 수 있기 때문에 함수 리터럴이라고 할 수 있다.
    • 당연한 이야기이지만, 함수를 간단하게 정의할 수 있는 방법으로 고안되었기 때문에 함수 선언문과 같이 funcion 키워드를 이용하지 않고 보다 간단한 방법을 제공하는 것이다.
  • 매개변수

    • 매개변수가 여러 개인 경우 소괄호를 묶어주지만, 한 개인 경우 생략 가능하다. 하지만 매개변수가 없는 경우는 생략할 수 없다
  • 함수 몸체

    • 함수 몸체 내부는 값으로 평가될 수 있는 표현식인 문을 중괄호로 묶어야 한다.

    • 함수 몸체 내부의 여러 문이 있는 경우, 반환값이 있다면 명시적으로 반환해야 한다

    • 1개의 표현식인 문으로 이뤄지면 중괄호를 생략할 수 있지만, 1개의 문이라도 표현식이 아니라면 생략할 수 없다.

      • 중괄호를 생략하면 암묵적으로 return 문이 있는 중괄호로 해석하여 값을 반환한다.
      • 그러나 값으로 평가될 수 없는 문에 중괄호가 없는 경우 잘못된 값이 반환되어 에러가 발생한다.
    • 객체 리터럴의 경우 객체 리터럴을 소괄호로 묶어주어야 한다.

      const create = (id, content) => ({ id, content });
      create(1, 'JavaScript'); //  {id: 1, content: "JavaScript"}
      
      // 위 표현은 다음과 동일하다. 중괄호를 생략하면 암묵적으로 값을 평가하여 반환 
      const create = (id, content) => { return { id, content }; };
  • 화살표 함수 역시 일급 객체이므로, 고차 함수에 인수로 전달 가능하다.


화살표 함수 (2) - 다른 함수와 차이점

화살표 함수는 크게 3가지 정도 다른 함수와 차이점을 구분할 수 있다.

1. 생성자 함수로 호출할 수 없다 

2. 중복된 매개변수를 선언할 수 없다 

3. this, arguments, new.target, super 바인딩을 갖지 않는다 

1. 생성자 함수로 호출할 수 없다

생성자 함수로 호출할 수 없다는 건, 화살표 함수를 통해 인스턴스(객체)를 생성할 수 없다는 의미이다. 자바스크립트는 프로토타입 기반 객체 지향을 지원하므로, 모든 객체는 프로토타입을 상속 받는다.

이 때, 생성자 함수를 통해 사용자가 정의한 생성자 함수와 생성자 프로토타입을 만들어 인스턴스(객체)에 상속시킬 수 있다. 이를 통해 사용자가 정의한 프로토타입 메서드 및 프로퍼티를 인스턴스에 상속시켜, 객체의 확장성을 구현하도록 지원한다.

이런 생성자 함수가 되기 위해서는 함수 내부 메서드 중 [[Construct]] 메서드가 존재해야 한다.

함수는 일반 객체와 구별되는 내부 메서드와 내부 프로퍼티가 존재한다. 그 중 모든 함수가 공통적으로 가지고 있는 [[Call]] 내부 메서드는 함수 객체가 함수를 호출할 수 있도록 만들어 준다.

[[Construct]]는 생성자 함수로 호출할 수 있는 함수 객체만 보유하고 있는 메서드이다. 따라서 화살표 함수로 생성된 함수 객체는 내부 메서드 [[Construct]] 가 없으므로 인스턴스를 생성할 수 없다.


2. 중복된 매개변수 선언 불가

일반 함수에서 중복된 매개변수를 선언하여도 문법 오류가 발생하지 않는다.

function sayHi(name, name){
  ...
}

물론 strict mode 에서는 일반 함수여도 중복된 매개변수를 허용하지 않는다.
하지만 화살표 함수는 strict mode 상관 없이 중복된 매개변수를 사용한 경우 문법 에러(Syntax Error)를 발생시킨다.


3. 바인딩

위에서 말한 것처럼 화살표 함수는 일반 함수와 달리 함수 정의 방법 뿐만 아니라 내부 동작 방식도 간소화 되었다고 말했던 것을 기억할 것이다. 바로 화살표 함수는 일반 함수와 달리 this, arguments, new.target, super 바인딩을 갖지 않는다.

this 바인딩

this 는 자신을 호출한 객체 또는 자신이 생성할 인스턴스를 가르키는 자기 참조 변수이다. 즉, this는 함수를 호출하는 방식에 따라 그 바인딩 대상이 동적으로 달라진다. 메서드로 호출하는 경우 자신을 호출한 객체로, 생성자 함수로 호출할 경우 생성자 함수가 생성할 인스턴스로 바인딩 된다.

ES6에서 메서드는 메서드 축약표현으로 정의한 프로퍼티만을 메서드라고 표현한다. 따라서 화살표 함수와 메서드로 정의한 함수는 엄연히 다르다.

또 위에서 말했듯 화살표 함수는 생성자 함수로 호출할 수 없기 때문에, 결론적으로 this 바인딩을 할 수 없다.

정확히 말해서 객체 내부에 this 바인딩 갖지 않기 때문에, 자신의 가장 가까운 상위 스코프 중 화살표 함수가 아닌 함수의 this를 참조한다.

이러한 특징 때문에 화살표 함수는 콜백 함수와 외부함수가 참조하는 this가 달라 생기는 문제를 해결하는데 유용하게 사용된다.

this를 참조하는 콜백 함수를 화살표 함수로 정의할 경우, this 바인딩을 갖지 않으므로 외부 함수가 정의된 스코프 상의 this와 동일한 this를 참조하게 된다.

class Prefixer {
 constructor(prefix) {
	 this.prefix = prefix;
 }

 add(arr) {
	 // 화살표 함수는 자신의 정의된 위치에 따라 add 함수 스코프 하위에 존재하며, 해당 스코프의 this는 클래스가 생성할 인스턴스이므로 콜백 함수와 외부 함수의 this가 일치한다. 
	 return arr.map(item => this.prefix + item);
 }
}

arguments

arguments는 유사배열 객체로 배열과 같이 객체 내부의 프로퍼티를 for문을 이용해 참조할 수 있는 객체이다. 이 객체 안에는 함수를 호출할 때 전달한 인수의 값이 인덱스를 키로 저장되어 있다.

하지만 화살표 함수로 정의한 함수의 경우, arguments 바인딩이 존재하지 않으므로 해당 화살표 함수의 상위 스코프의 arguments를 참조하게 된다.

즉, 자신에게 전달된 인수의 값이 아니라 자신의 상위 스코프 함수에 전달된 인수의 값을 참조한다는 의미는 화살표 함수 내부에서 arguments를 참조할 의미가 없다는 것이다.(왜냐하면 자신에게 전달된 인수가 아니므로 참조할 이유가 없기 때문이다)

따라서 화살표 함수의 경우 rest 파라미터를 이용해, 전달된 인수 정보를 확인할 수 있다.

const print = (...args) => args.forEach(e => console.log(e)); 

화살표 함수의 의의

이와 같이 화살표 함수는 일반 함수와 명확히 다르게 동작한다. 따라서 화살표 함수를 사용하는 그 목적에 따라 적절히 사용할 수 있어야 한다.

화살표 함수는 일반 함수와 달리 프로토타입 객체를 생성하지 않으므로, 메모리 관점에서 더 유리하다. 프로토타입 객체를 생성하지 않는다는 것은, 화살표 함수로 정의한 함수는 오직 함수로만 호출해야 한다는 의미이다.

이는 사용자로 하여금 실수와 혼란을 방지할 수 있도록 만든다는 점에서 의의가 있다.

profile
배운 것을 기록하는 FrontEnd Junior 입니다

0개의 댓글