화살표 함수 표현식

화살표 함수 표현식 (Arrow function expressions)

화살표 함수 표현식은 기존 함수 표현식에 대한 간결한 대안으로, 몇 가지 의미적 차이와 의도적인 사용 제한이 있습니다. Baseline Widely available — 2016년 9월부터 여러 브라우저에서 지원되는 잘 정착된 기능입니다.

출처: Arrow function expressions

본문

화살표 함수 표현식은 전통적인 함수 표현식에 대한 간결한 대안으로, 몇 가지 의미적 차이와 의도적인 사용 제한이 있습니다:

  • 화살표 함수는 this, arguments, super에 대한 자체 바인딩이 없으며, 메서드로 사용해서는 안 됩니다.
  • 화살표 함수는 생성자로 사용할 수 없습니다. new로 호출하면 TypeError가 발생합니다. 또한 new.target 키워드에 접근할 수 없습니다.
  • 화살표 함수는 본문 안에서 yield를 사용할 수 없으며 제너레이터 함수로 생성될 수 없습니다.

시도해 보기 (Try it)

const materials = ["Hydrogen", "Helium", "Lithium", "Beryllium"];

console.log(materials.map((material) => material.length));
// Expected output: Array [8, 6, 7, 9]

구문 (Syntax)

() => expression

param => expression

(param) => expression

(param1, paramN) => expression

() => {
  statements
}

param => {
  statements
}

(param1, paramN) => {
  statements
}

나머지 매개변수(rest parameters), 기본 매개변수(default parameters), 매개변수 안의 구조 분해가 지원되며, 항상 괄호가 필요합니다:

(a, b, ...r) => expression
(a = 400, b = 20, c) => expression
([a, b] = [10, 20]) => expression
({ a, b } = { a: 10, b: 20 }) => expression

화살표 함수는 표현식 앞에 async 키워드를 붙여 async로 만들 수 있습니다.

async param => expression
async (param1, param2, ...paramN) => {
  statements
}

설명 (Description)

전통적인 익명 함수를 단계별로 가장 단순한 화살표 함수로 분해해 봅시다. 각 단계는 유효한 화살표 함수입니다.

참고: 전통적인 함수 표현식과 화살표 함수는 구문보다 더 많은 차이점이 있습니다. 우리는 다음 소절들에서 그 동작 차이를 더 자세히 소개합니다.

// Traditional anonymous function
(function (a) {
  return a + 100;
});

// 1. Remove the word "function" and place arrow between the argument and opening body brace
(a) => {
  return a + 100;
};

// 2. Remove the body braces and word "return" — the return is implied.
(a) => a + 100;

// 3. Remove the parameter parentheses
a => a + 100;

위 예제에서 매개변수 주위의 괄호와 함수 본문 주위의 중괄호는 둘 다 생략될 수 있습니다. 그러나 특정 경우에만 생략할 수 있습니다.

괄호는 함수가 단일의 단순한 매개변수를 가질 때만 생략할 수 있습니다. 여러 매개변수, 매개변수가 없음, 또는 기본·구조 분해·나머지 매개변수가 있다면 매개변수 목록 주위의 괄호가 필요합니다.

// Traditional anonymous function
(function (a, b) {
  return a + b + 100;
});

// Arrow function
(a, b) => a + b + 100;

const a = 4;
const b = 2;

// Traditional anonymous function (no parameters)
(function () {
  return a + b + 100;
});

// Arrow function (no parameters)
() => a + b + 100;

중괄호는 함수가 직접 표현식을 반환할 때만 생략할 수 있습니다. 본문에 문(statements)이 있으면 중괄호가 필요합니다. 이 경우 반환값은 return 키워드로 명시적으로 지정해야 합니다. 화살표 함수는 무엇을 언제 반환하려는지 추측할 수 없습니다.

// Traditional anonymous function
(function (a, b) {
  const chuck = 42;
  return a + b + chuck;
});

// Arrow function
(a, b) => {
  const chuck = 42;
  return a + b + chuck;
};

화살표 함수는 본래 이름과 연관되지 않습니다. 화살표 함수가 스스로를 호출해야 한다면 대신 이름 있는 함수 표현식(named function expression)을 사용하세요. 또한 화살표 함수를 변수에 할당해 그 변수를 통해 참조할 수도 있습니다.

// Traditional Function
function bob(a) {
  return a + 100;
}

// Arrow Function
const bob2 = (a) => a + 100;

함수 본문 (Function body)

화살표 함수는 표현식 본문(expression body) 또는 일반적인 블록 본문(block body)을 가질 수 있습니다.

표현식 본문에서는 단일 표현식만 지정되며, 그것이 암시적 반환값이 됩니다. 블록 본문은 전통적인 함수 본문과 유사하며, 반환값은 return 키워드로 명시적으로 지정해야 합니다. 화살표 함수는 값을 반환할 필요가 없습니다. 블록 본문의 실행이 return 문을 만나지 않고 끝에 도달하면, 다른 함수들과 마찬가지로 함수는 undefined를 반환합니다.

// Expression body
const add = (a, b) => a + b; // Implicitly returns a + b

// Block body
const add2 = (a, b) => {
  console.log(a, b);
  return a + b; // Must explicitly return a value
};

// No return value
const add3 = (b) => {
  a += b;
  // No return statement, so returns undefined
};

표현식 본문 구문 (params) => { object: literal }으로 객체 리터럴을 반환하는 것은 예상대로 동작하지 않습니다.

const func = () => { foo: 1 };
// Calling func() returns undefined!

const func2 = () => { foo: function () {} };
// SyntaxError: function statement requires a name

const func3 = () => { foo() {} };
// SyntaxError: Unexpected token '{'

이는 화살표 뒤에 오는 토큰이 왼쪽 중괄호가 아닐 때만 JavaScript가 화살표 함수가 표현식 본문을 가진 것으로 보기 때문입니다. 따라서 중괄호({}) 안의 코드는 일련의 문으로 파싱되는데, 여기서 foo는 객체 리터럴의 키가 아니라 레이블(label)입니다.

이를 고치려면 객체 리터럴을 괄호로 감싸세요:

const func = () => ({ foo: 1 });

메서드로 사용할 수 없음 (Cannot be used as methods)

화살표 함수 표현식은 자체 this가 없으므로 메서드가 아닌 함수에만 사용해야 합니다. 메서드로 사용하려 할 때 어떤 일이 일어나는지 봅시다:

"use strict";

const obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c() {
    console.log(this.i, this);
  },
};

obj.b(); // logs undefined, Window { /* … */ } (or the global object)
obj.c(); // logs 10, Object { /* … */ }

Object.defineProperty()가 포함된 또 다른 예제:

"use strict";

const obj = {
  a: 10,
};

Object.defineProperty(obj, "b", {
  get: () => {
    console.log(this.a, typeof this.a, this); // undefined 'undefined' Window { /* … */ } (or the global object)
    return this.a + 10; // represents global object 'Window', therefore 'this.a' returns 'undefined'
  },
});

클래스의 본문은 this 컨텍스트를 가지므로, 클래스 필드로서의 화살표 함수는 클래스의 this 컨텍스트를 폐쇄(close over)하며, 화살표 함수 본문 안의 this는 인스턴스(정적 필드의 경우 클래스 자체)를 올바르게 가리킵니다. 그러나 그것은 클로저이지 함수의 자체 바인딩이 아니므로, this의 값은 실행 컨텍스트에 따라 변하지 않습니다.

class C {
  a = 1;
  autoBoundMethod = () => {
    console.log(this.a);
  };
}

const c = new C();
c.autoBoundMethod(); // 1
const { autoBoundMethod } = c;
autoBoundMethod(); // 1
// If it were a normal method, it should be undefined in this case

화살표 함수 속성은 종종 "자동 바인딩 메서드(auto-bound methods)"라고 불리는데, 일반 메서드로 표현하면 다음과 같기 때문입니다:

class C {
  a = 1;
  constructor() {
    this.method = this.method.bind(this);
  }
  method() {
    console.log(this.a);
  }
}

참고: 클래스 필드는 프로토타입이 아니라 인스턴스에 정의되므로, 인스턴스를 만들 때마다 새 함수 참조가 생성되고 새 클로저가 할당되어, 일반적인 바인딩되지 않은 메서드보다 메모리 사용량이 늘어날 수 있습니다.

비슷한 이유로 call(), apply(), bind() 메서드는 화살표 함수에 호출될 때 유용하지 않습니다. 화살표 함수는 정의된 스코프에 기반해 this를 설정하고, this 값은 함수가 어떻게 호출되는지에 따라 변하지 않기 때문입니다.

arguments의 바인딩 없음 (No binding of arguments)

화살표 함수는 자체 arguments 객체가 없습니다. 따라서 이 예제에서 arguments는 둘러싼 스코프의 arguments에 대한 참조입니다:

function foo(n) {
  const f = () => arguments[0] + n; // foo's implicit arguments binding. arguments[0] is n
  return f();
}

foo(3); // 3 + 3 = 6

대부분의 경우 arguments 객체를 사용하는 대신 나머지 매개변수를 사용하는 것이 좋은 대안입니다.

function foo(n) {
  const f = (...args) => args[0] + n;
  return f(10);
}

foo(1); // 11

생성자로 사용할 수 없음 (Cannot be used as constructors)

화살표 함수는 생성자로 사용할 수 없으며 new로 호출하면 오류가 발생합니다. 또한 prototype 속성도 없습니다.

const Foo = () => {};
const foo = new Foo(); // TypeError: Foo is not a constructor
console.log("prototype" in Foo); // false

제너레이터로 사용할 수 없음 (Cannot be used as generators)

yield 키워드는 화살표 함수의 본문에서 사용할 수 없습니다(화살표 함수 안에 더 중첩된 제너레이터 함수 내에서 사용할 때는 제외). 결과적으로 화살표 함수는 제너레이터로 사용할 수 없습니다.

화살표 앞 줄바꿈 (Line break before arrow)

화살표 함수는 매개변수와 화살표 사이에 줄바꿈을 포함할 수 없습니다.

const func = (a, b, c)
  => 1;
// SyntaxError: Unexpected token '=>'

서식 목적으로 화살표 뒤에 줄바꿈을 넣거나 함수 본문 주위에 괄호/중괄호를 사용할 수 있습니다(아래 참고). 매개변수 사이에 줄바꿈을 넣을 수도 있습니다.

const func = (a, b, c) =>
  1;

const func2 = (a, b, c) => (
  1
);

const func3 = (a, b, c) => {
  return 1;
};

const func4 = (
  a,
  b,
  c,
) => 1;

화살표의 우선순위 (Precedence of arrow)

화살표 함수의 화살표는 연산자가 아니지만, 화살표 함수는 일반 함수와 비교해 연산자 우선순위와 다르게 상호작용하는 특별한 파싱 규칙을 가집니다.

let callback;

callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments

=>는 대부분의 연산자보다 우선순위가 낮기 때문에, callback || ()가 화살표 함수의 인자 목록으로 파싱되는 것을 피하려면 괄호가 필요합니다.

callback = callback || (() => {});

예제 (Examples)

화살표 함수 사용하기

// An empty arrow function returns undefined
const empty = () => {};

(() => "foobar")();
// Returns "foobar"
// (this is an Immediately Invoked Function Expression)

const simple = (a) => (a > 15 ? 15 : a);
simple(16); // 15
simple(10); // 10

const max = (a, b) => (a > b ? a : b);

// Easy array filtering, mapping, etc.
const arr = [5, 6, 13, 0, 1, 18, 23];

const sum = arr.reduce((a, b) => a + b);
// 66

const even = arr.filter((v) => v % 2 === 0);
// [6, 0, 18]

const double = arr.map((v) => v * 2);
// [10, 12, 26, 0, 2, 36, 46]

// More concise promise chains
promise
  .then((a) => {
    // …
  })
  .then((b) => {
    // …
  });

// Arrow functions without parameters
setTimeout(() => {
  console.log("I happen sooner");
  setTimeout(() => {
    // deeper code
    console.log("I happen later");
  }, 1);
}, 1);

call, bind, apply 사용하기

call(), apply(), bind() 메서드는 전통적인 함수와 기대대로 동작합니다. 각 메서드에 대한 스코프를 설정하기 때문입니다:

const obj = {
  num: 100,
};

// Setting "num" on globalThis to show how it is NOT used.
globalThis.num = 42;

// A traditional function to operate on "this"
function add(a, b, c) {
  return this.num + a + b + c;
}

console.log(add.call(obj, 1, 2, 3)); // 106
console.log(add.apply(obj, [1, 2, 3])); // 106
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 106

화살표 함수에서는, 우리의 add 함수가 본질적으로 globalThis(전역) 스코프에서 생성되었으므로 thisglobalThis라고 가정합니다.

const obj = {
  num: 100,
};

// Setting "num" on globalThis to show how it gets picked up.
globalThis.num = 42;

// Arrow function
const add = (a, b, c) => this.num + a + b + c;

console.log(add.call(obj, 1, 2, 3)); // 48
console.log(add.apply(obj, [1, 2, 3])); // 48
const boundAdd = add.bind(obj);
console.log(boundAdd(1, 2, 3)); // 48

화살표 함수를 사용할 때의 가장 큰 이점은 setTimeout()EventTarget.prototype.addEventListener() 같은, 보통 함수가 적절한 스코프에서 실행되도록 어떤 종류의 클로저, call(), apply(), bind()가 필요한 메서드와 함께 쓸 때입니다.

전통적인 함수 표현식에서는 이런 코드가 기대대로 동작하지 않습니다:

const obj = {
  count: 10,
  doSomethingLater() {
    setTimeout(function () {
      // the function executes on the window scope
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // logs "NaN", because the property "count" is not in the window scope.

화살표 함수에서는 this 스코프가 더 쉽게 보존됩니다:

const obj = {
  count: 10,
  doSomethingLater() {
    // The method syntax binds "this" to the "obj" context.
    setTimeout(() => {
      // Since the arrow function doesn't have its own binding and
      // setTimeout (as a function call) doesn't create a binding
      // itself, the "obj" context of the outer method is used.
      this.count++;
      console.log(this.count);
    }, 300);
  },
};

obj.doSomethingLater(); // logs 11

명세 (Specifications)

화살표 함수는 ECMAScript 언어 명세에 정의되어 있습니다.

브라우저 호환성 (Browser compatibility)

이 기능은 대부분의 현대 브라우저에서 널리 지원됩니다(Baseline Widely available). 자세한 호환성 표는 MDN의 브라우저 호환성 섹션을 참고하세요.

더 알아보기