자바스크립트 화살표 함수: 더 간결하고 직관적인 코드 작성법

ClydeHan·2024년 9월 18일
9

자바스크립트 화살표 함수(Arrow Functions)

Arrow function image

이미지 출처: vietnamlife.info


화살표 함수: 함수 표현식의 대체 방식

화살표 함수(Arrow Function)는 ES6(ECMAScript 2015)에서 도입된 새로운 함수 선언 방식이다. 기존의 함수 선언 방식인 function 키워드 대신 화살표(=>)를 사용하여 더 짧고 간결하게 함수를 정의할 수 있다.

화살표 함수는 함수 표현식의 대체 방식으로, 함수 선언을 더 간결하게 할 수 있게 해준다. 화살표 함수는 function 키워드를 사용하지 않고, => 구문을 사용하여 함수를 정의한다. 화살표 함수는 함수 표현식의 한 형태이며, 렉시컬 this를 사용하는 것이 특징이다.

간결한 문법을 제공하지만, 함수 선언문과는 구분된다. 화살표 함수는 this 바인딩이나 arguments 객체를 가지지 않으며, 함수 선언문과 달리 호이스팅되지 않는다.

정리하자면, 화살표 함수는 함수 선언을 간결하게 하고, this동적으로 바뀌지 않고 고정되는 장점이 있다.

const add = (a, b) => a + b;

✏️ 함수 선언문과 함수 표현식에 대한 자세한 설명
JavaScript 함수 선언식 vs 함수 표현식: 차이점과 성능 최적화 전략


📌 this

💡 렉시컬 this란?

렉시컬 this는 화살표 함수에서의 this어떻게 동작하는지를 설명하는 개념이다. 여기서 렉시컬이라는 단어는 어휘적이라는 뜻으로, 코드가 작성된 위치에 따라 this가 고정된다는 의미이다.

쉽게 설명하자면, 화살표 함수에서는 this가 함수 호출 방식에 따라 달라지지 않고, 함수가 정의된 위치에 있는 this를 그대로 사용한다. 이를 렉시컬 this라고 부르며, 함수가 호출되는 방식과 상관없이 상위 스코프의 this를 고정적으로 참조한다.

이를 이해하기 위해, 먼저 전통적인 함수에서 this가 어떻게 동작하는지 살펴보자.

✏️ 렉시컬 환경에 대한 자세한 설명
[JavaScript] 클로저와 렉시컬 환경 (Closure, Lexical Environment)


💡 전통적인 함수에서의 this

일반적인 함수(function 키워드로 선언된 함수)에서 this함수가 호출되는 방식에 따라 달라진다. 호출하는 주체(객체)에 따라 this가 가리키는 대상이 달라지므로, 같은 함수라도 호출 방법에 따라 다르게 동작할 수 있다.

예시: 전통적인 함수에서의 this

const person = {
  name: 'John',
  greet: function() {
    console.log(this.name); // this는 person 객체를 가리킴
  }
};

person.greet(); // "John"

위 코드에서 greet 함수는 객체 person의 메서드로 호출되었기 때문에 thisperson 객체를 가리킨다. 그래서 this.nameJohn이 출력된다.

하지만, 아래와 같이 함수가 독립적으로 호출될 때는 상황이 달라진다.

const greet = person.greet;
greet(); // undefined

이 경우 greet 함수를 객체에서 분리하여 호출했기 때문에 this는 더 이상 person 객체가 아닌 전역 객체(브라우저에서는 window)를 가리킨다. 그래서 this.nameundefined가 된다.


💡 화살표 함수에서의 렉시컬 this

화살표 함수에서는 이런 복잡한 this 바인딩 규칙이 없다. 화살표 함수의 this는 함수가 정의된 시점상위 스코프(외부 환경)에서 자동으로 고정된다. 이를 렉시컬 this라고 한다. 즉, 어디서 호출되든 상관없이 this는 변하지 않고, 함수가 작성된 위치의 this를 계속 참조한다.

예시: 화살표 함수에서의 this

const person = {
  name: 'John',
  greet: () => {
    console.log(this.name);
  }
};

person.greet(); // undefined

여기서 화살표 함수 greetperson 객체 내에 정의되었지만, thisgreet 함수가 정의된 시점상위 스코프전역 스코프를 참조한다. 따라서 this.name은 전역 객체의 name 속성을 가리키고, 대부분의 경우 전역 객체에 name 속성이 없으므로 undefined가 출력된다.


💡 예시: 화살표 함수의 렉시컬 this가 유용할 때

렉시컬 this의 장점은 특히 비동기 작업이나 콜백 함수에서 두드러진다. 전통적인 함수에서는 this를 유지하기 위해 bindself = this 같은 패턴을 사용해야 했지만, 화살표 함수는 이런 번거로움이 없다.

const person = {
  name: 'John',
  greet() {
    setTimeout(() => {
      console.log(this.name); // this는 person 객체를 가리킴
    }, 1000);
  }
};

person.greet(); // 1초 후 "John" 출력

위 코드에서 setTimeout은 비동기 함수이기 때문에, 전통적인 함수라면 this가 전역 객체로 바뀌는 문제가 발생할 수 있다. 그러나 화살표 함수는 this정의된 시점this를 참조하므로, setTimeout 안에서도 thisperson 객체를 가리키고, this.name은 올바르게 John을 출력한다.


💡 화살표 함수의 this 바인딩

화살표 함수는 전통적인 함수와 달리 this 바인딩을 가지지 않는다. 즉, 화살표 함수 자체의 this가 존재하지 않으며, 렉시컬 this에 의해 상위 스코프this를 참조하게 된다. 이 특징은 이미 렉시컬 this 설명에서 다루었듯이, 화살표 함수는 호출되는 방식에 관계없이 상위 스코프의 this를 고정해서 사용한다.

const person = {
  name: 'John',
  greet: () => {
    console.log(this.name); // 전역 객체의 this를 참조
  }
};

person.greet(); // undefined

위 코드에서 화살표 함수는 this가 없다. 따라서 greet 메서드는 전역 스코프this를 참조하고, 결과적으로 this.nameundefined가 출력된다.


📌 arguments

화살표 함수는 this가 고정된다는 특징 외에도, arguments 객체를 지원하지 않는다. 대신, 나머지 매개변수를 사용해 동일한 기능을 구현할 수 있다.


💡 화살표 함수와 arguments 객체

arguments 객체는 함수가 호출될 때 전달된 모든 인자들을 담고 있는 특별한 객체이다. 하지만 이 arguments 객체는 화살표 함수에서는 사용되지 않는다. 대신, 나머지 매개변수를 사용해야 한다.


💡 arguments 객체란?

arguments 객체는 함수가 호출될 때 함수에 전달된 모든 인자들을 배열처럼 저장하는 객체이다. 예를 들어, 함수에 3개의 인자를 전달하면, arguments 객체는 그 3개의 값을 모두 담고 있는 일종의 리스트라고 생각하면 된다.

function sum() {
  return Array.from(arguments).reduce((acc, val) => acc + val, 0);
}

console.log(sum(1, 2, 3)); // 6

위 코드를 보면, sum 함수는 인자를 3개 받았고, arguments 객체를 사용해 이 인자들을 모두 더하고 있다. 여기서 중요한 것은, 함수가 몇 개의 인자를 받든 arguments 객체가 자동으로 그 모든 인자를 관리해준다는 것이다.


💡 화살표 함수에서는 arguments 객체 대신 나머지 매개변수 사용

화살표 함수에서는 arguments 객체를 사용할 수 없다. 그래서 나머지 매개변수라는 방식을 사용해야 한다. 나머지 매개변수는 ...args와 같은 형식으로 사용하며, 이것도 전달된 모든 인자들을 배열 형태로 관리해준다.

const sum = (...args) => args.reduce((acc, val) => acc + val, 0);

console.log(sum(1, 2, 3)); // 6

위 코드는 화살표 함수에서 ...args를 사용해 전달된 인자들을 모두 더한 예시이다. arguments와 달리, 이 방식은 배열처럼 다루기 때문에 훨씬 유연하고 직관적이다.


📌 왜 화살표 함수를 사용해야 하는가?

화살표 함수는 다음과 같은 이유로 자주 사용된다.

💡 간결한 문법

화살표 함수는 문법이 간결하여 코드의 가독성을 높여준다. 특히 콜백 함수나 간단한 연산을 처리할 때 유용하다.

// 전통적인 함수 선언
function add(a, b) {
  return a + b;
}

// 화살표 함수
const add = (a, b) => a + b;

위의 예시에서 볼 수 있듯이, function 키워드와 return 문을 생략하고 한 줄로 작성할 수 있어 코드가 훨씬 깔끔해진다.


💡 렉시컬 this 바인딩

화살표 함수는 this가 정의된 스코프를 유지하는 렉시컬 this를 가진다. 전통적인 함수에서는 함수 호출 방식에 따라 this가 동적으로 변경되지만, 화살표 함수는 이를 방지하여 직관적인 this 사용이 가능하다.

function Timer() {
  this.seconds = 0;

  // 전통적인 함수
  setInterval(function() {
    this.seconds++; // 오류 발생: this가 setInterval의 컨텍스트를 참조
  }, 1000);
}

function TimerFixed() {
  this.seconds = 0;

  // 화살표 함수
  setInterval(() => {
    this.seconds++; // 올바르게 작동: this는 TimerFixed 인스턴스를 참조
  }, 1000);
}

화살표 함수는 this를 주변 스코프에서 그대로 가져오기 때문에, bind 메서드나 self = this 같은 패턴을 사용하지 않아도 된다.


📌 화살표 함수의 문법

💡 매개변수가 하나일 때

매개변수가 하나일 경우 괄호(())를 생략할 수 있다.

// 매개변수가 하나일 때 괄호 생략 가능
const square = x => x * x;

💡 매개변수가 없을 때

매개변수가 없을 때는 반드시 빈 괄호를 사용해야 한다.

const sayHello = () => console.log('Hello!');

💡 본문이 한 줄일 때

본문이 한 줄일 경우 중괄호({})와 return을 생략할 수 있다.

const multiplyByTwo = num => num * 2;

💡 본문이 여러 줄일 때

본문이 여러 줄일 경우 중괄호를 사용하며, 명시적으로 return을 작성해야 한다.

const multiply = (a, b) => {
  const result = a * b;
  return result;
};

📌 화살표 함수의 제한 사항

💡 생성자로 사용할 수 없다

화살표 함수는 new 키워드를 사용하여 생성자로 호출할 수 없다. 이는 화살표 함수가 prototype 속성을 가지고 있지 않기 때문이다.

const Person = (name) => {
  this.name = name;
};

// 오류 발생: Person은 생성자가 아님
const person = new Person('John');

생성자로 사용할 함수는 반드시 function 키워드를 사용하여 정의해야 한다.


💡 arguments 객체를 사용할 수 없다

화살표 함수는 arguments 객체를 지원하지 않는다. 대신, 필요한 경우에는 나머지 매개변수(rest parameter)를 사용할 수 있다.

// 전통적인 함수에서 arguments 사용
function sum() {
  return Array.from(arguments).reduce((total, num) => total + num, 0);
}

// 화살표 함수에서 rest parameter 사용
const sum = (...args) => args.reduce((total, num) => total + num, 0);

💡 메서드로 사용하기 적합하지 않다

화살표 함수는 this가 고정되어 있어, 객체의 메서드로 사용하기에는 부적합하다. 객체 메서드에서는 function 키워드를 사용하는 것이 더 적합하다.

const person = {
  name: 'Alice',
  greet: () => {
    console.log(`Hello, ${this.name}`); // this는 전역 객체를 참조
  }
};

person.greet(); // 'Hello, undefined'

위 코드에서는 this가 객체를 가리키지 않기 때문에, name 속성에 접근할 수 없다. 객체의 메서드에서는 일반 함수 표현식을 사용하는 것이 올바른 방법이다.


📌 화살표 함수의 활용 사례

💡 콜백 함수로 사용

const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2); // [2, 4, 6]

💡 비동기 작업 처리

const fetchData = () => {
  setTimeout(() => {
    console.log('Data fetched');
  }, 1000);
};

fetchData(); // 1초 후에 'Data fetched' 출력

💡 배열 메서드에서의 화살표 함수

const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2); // [2, 4, 6]

💡 이벤트 핸들링

document.querySelector('button').addEventListener('click', () => {
  console.log('Button clicked');
});

💡 리액트에서 이벤트 핸들링

function MyComponent() {
  const handleClick = () => {
    console.log('Clicked!');
  };

  return <button onClick={handleClick}>Click me</button>;
}

📌 화살표 함수와 전통적인 함수의 비교

특성전통적인 함수화살표 함수
this 바인딩호출 방식에 따라 동적으로 변경렉시컬 스코프에 고정된 this
문법function 키워드를 사용=> 문법을 사용
arguments 사용지원미지원 (rest parameter 사용)
생성자 사용가능불가능

참고문헌

0개의 댓글