본문으로 건너뛰기
김신건의 로그

[Javascript] Arrow Function

· 수정 · 📖 약 4분 · 1,390자/단어 #javascript #function #syntax #es6
arrow function, Arrow Function, JavaScript arrow function, JS arrow function, 화살표 함수, arrow-function, fat arrow

정의

화살표 함수 (arrow function) 는 ES2015 (ES6) 에서 도입된 함수 표현식. () => {} 모양. 더 짧은 문법뿐 아니라 this 가 lexical 로 바인딩 되는 본질적 차이가 있다.

function (일반 함수) 와의 차이가 핵심.

문법 변형 (실행 결과로 확인)

화살표 함수의 모든 모양
node
// 인자 0개
const greet = () => 'hello';
console.log(greet());

// 인자 1개 (괄호 생략 가능)
const double = x => x * 2;
console.log(double(5));

// 인자 여러 개
const add = (x, y) => x + y;
console.log(add(3, 4));

// 본문이 블록 (명시적 return 필요)
const sumAndDouble = (x, y) => {
const sum = x + y;
return sum * 2;
};
console.log(sumAndDouble(3, 4));

// 객체 반환 (괄호로 감싸기)
const makePoint = (x, y) => ({ x, y });
console.log(makePoint(1, 2));
출력
hello
10
7
14
{ x: 1, y: 2 }

CAUTION

객체 리터럴 반환 시 {} 가 함수 본문 블록으로 해석되지 않도록 반드시 괄호로 감싸야 한다.

❌ 객체 반환 함정
javascript
const makeWrong = (x) => { value: x };
console.log(makeWrong(42));

const makeRight = (x) => ({ value: x });
console.log(makeRight(42));
결과
undefined
{ value: 42 }

첫 번째에선 { value: x } 가 블록으로 파싱되고, value: 는 label, x 는 결과를 안 쓰는 표현문이 되어 함수가 undefined 를 반환한다.

function 과의 결정적 차이

1. this 가 lexical (정의 시점)

화살표 함수는 자기 this 가 없다. 정의된 위치의 this 를 그대로 사용 (스코프 체인 위로 올라감). 이게 화살표 함수의 가장 본질적인 차이이며, Lexical Environment 개념에서 자라난다.

일반 함수의 this 는 호출 시 결정
javascript
const obj = {
name: 'Alice',
greetRegular: function () {
  return `Hello, ${this.name}`;
},
};

console.log(obj.greetRegular()); // obj 가 this

const fn = obj.greetRegular;
console.log(fn()); // this 는 undefined (strict)
결과
Hello, Alice
Hello, undefined

같은 함수 객체인데 호출 방식 에 따라 this 가 달라진다. obj.greetRegular()obj 가 호출 맥락이지만, fn() 은 단순 호출이라 thisundefined.

화살표 함수의 this 는 정의 시 결정
javascript
const name = 'Global';

const obj = {
name: 'Alice',
greetArrow: () => `Hello, ${this?.name ?? globalThis.name}`,
};

console.log(obj.greetArrow());

const fn = obj.greetArrow;
console.log(fn()); // 같은 결과, 호출 방식 무관
결과
Hello, Global
Hello, Global

화살표 함수의 this 는 정의 위치 (모듈 최상위) 의 this 에 묶였다. obj. 로 부르든 단독으로 부르든 같은 결과.

2. arguments 가 없다

arguments 객체의 유무
javascript
function regular() {
console.log('regular:', Array.from(arguments));
}
regular(1, 2, 3);

// 화살표는 arguments 가 외부 스코프를 본다
const arrow = () => {
try {
  console.log('arrow:', arguments);
} catch (e) {
  console.log('arrow:', e.message);
}
};
arrow(1, 2, 3);

// rest 매개변수가 정답
const arrowRest = (...args) => console.log('rest:', args);
arrowRest(1, 2, 3);
결과
regular: [ 1, 2, 3 ]
arrow: arguments is not defined
rest: [ 1, 2, 3 ]

3. new 호출 불가

생성자 사용 가능 여부
javascript
function Person(name) {
this.name = name;
}
const a = new Person('Alice');
console.log(a.name);

const Arrow = (name) => { this.name = name; };
try {
new Arrow('Bob');
} catch (e) {
console.log('Error:', e.message);
}
결과
Alice
Error: Arrow is not a constructor

화살표 함수는 prototype 자체가 없어 new 로 호출할 수 없다.

4. 호이스팅 안 됨

호이스팅 차이
javascript
// 함수 선언은 호이스팅됨
greet('A');
function greet(n) { console.log('hi', n); }

// 화살표는 표현식, TDZ
try {
sayHi('B');
} catch (e) {
console.log('Error:', e.message);
}
const sayHi = (n) => console.log('hi', n);
결과
hi A
Error: Cannot access 'sayHi' before initialization

역사적 사례, const self = this 트릭

ES5 시절 (2009-2015), 화살표 함수가 없었을 때 콜백에서 외부 this 를 보존하려면 명시적으로 임시 변수에 담는 패턴이 표준이었다. 보통 변수 이름을 self, _this, that 으로 썼다.

그 시절 코드 (jQuery / Backbone 시대)

2010년대 초반의 전형적인 패턴
ES5
function Counter() {
this.count = 0;
var self = this;  // ← 트릭의 시작

setInterval(function () {
  // 여기서 this 는 setInterval 의 호출 컨텍스트 (브라우저: Window)
  // self 가 외부 Counter 인스턴스를 들고 있다
  self.count++;
  console.log('count:', self.count);
}, 1000);
}

new Counter();
결과
count: 1
count: 2
count: 3
... (1초마다)

여기서 self = this 를 하지 않았다면:

❌ self 트릭 없이 (typical ES5 버그)
javascript
function Counter() {
this.count = 0;

setInterval(function () {
  this.count++; // ❌ this 는 Window
  // strict 모드면 TypeError
  // sloppy 모드면 window.count 가 생성되며 의도와 다른 동작
  console.log('count:', this.count);
}, 1000);
}

new Counter();
결과
count: NaN
count: NaN
count: NaN
... (window.count = undefined + 1 = NaN)

Counterthis (인스턴스) 가 콜백 안에서는 Window 가 되어 의도한 카운터가 동작하지 않는다.

.bind() 도 한 방법이었다

ES5 의 Function.prototype.bind 도 같은 목적으로 쓰였다.

.bind(this) 패턴
ES5
function Counter() {
this.count = 0;

setInterval(function () {
  this.count++;
  console.log('count:', this.count);
}.bind(this), 1000);  // ← 매 호출마다 새 함수 객체 생성
}

new Counter();
결과
count: 1
count: 2
count: 3
...

bindself 보다 조금 더 명시적이지만 매번 새 함수 객체를 만들어 메모리 부담이 있고, 중첩 콜백에서는 .bind(this).bind(this) 같이 보기 흉해졌다.

ES6, 화살표 함수가 두 패턴을 모두 대체

✅ 현대 (ES6+) 의 동일 코드
ES6+
class Counter {
constructor() {
  this.count = 0;
  setInterval(() => {
    this.count++;
    console.log('count:', this.count);
  }, 1000);
}
}

new Counter();
결과
count: 1
count: 2
count: 3
...
  • self = this 같은 임시 변수 불필요
  • .bind(this) 의 새 함수 객체 비용 없음
  • this 가 정의 위치 (constructor 의 this = 인스턴스) 를 그대로 사용

jQuery 시절의 흔한 패턴

DOM 이벤트, ES5 → ES6 비교
function ButtonWidget(el) {
this.label = 'Click me';
this.clicks = 0;
var self = this;

$(el).on('click', function () {
  self.clicks++;
  $(el).text(self.label + ' (' + self.clicks + ')');
});
}
동작
(클릭마다 라벨 갱신, 두 코드 동등)

TIP

옛 코드베이스를 마이그레이션할 때 var self = this 또는 var _this = this 패턴을 보면, 거의 다음 콜백을 화살표 함수로 바꿔서 제거할 수 있다.


빠른 비교표

특성function() => {}
this동적 (호출 시)lexical (정의 시)
arguments
new
prototype
super✓ (메서드에서)lexical
yield✓ (function* 으로)
호이스팅✓ (선언문)
짧은 문법
객체 메서드로 적합✗ (this 가 lexical)
콜백으로 적합△ (this 주의)

자주 쓰이는 곳

1. 배열 메서드의 콜백

map / filter / reduce
javascript
const nums = [1, 2, 3, 4, 5];

console.log(nums.map(x => x * 2));
console.log(nums.filter(x => x % 2 === 0));
console.log(nums.reduce((acc, x) => acc + x, 0));
결과
[ 2, 4, 6, 8, 10 ]
[ 2, 4 ]
15

2. Promise 체인 / async

fetch('/api/users')
  .then(r => r.json())
  .then(users => users.map(u => u.name))
  .catch(err => console.error(err));

3. 즉시 실행 함수 (IIFE)

const result = (() => {
  const x = compute();
  return process(x);
})();

언제 화살표 함수를 피해야 하는가

객체 메서드

❌ 화살표를 메서드로 쓰면
javascript
const obj = {
name: 'Alice',
greet: () => `Hello, ${this?.name ?? 'unknown'}`,
};
console.log(obj.greet());

// ✓ 메서드 단축 문법
const obj2 = {
name: 'Alice',
greet() { return `Hello, ${this.name}`; },
};
console.log(obj2.greet());
결과
Hello, unknown
Hello, Alice

프로토타입 메서드 / 클래스 메서드

같은 이유. this 가 인스턴스를 가리켜야 한다. (단, 클래스 필드에 화살표를 할당하면 인스턴스마다 새 함수가 묶여서 this 가 인스턴스가 되므로 의도적 사용 가능.)

이벤트 핸들러 (this 가 element 여야 할 때)

이벤트 핸들러의 this
javascript
// 가상 DOM 환경
const button = { tag: 'BUTTON', textContent: '' };

function handlerRegular() {
console.log('this is:', this.tag);
}

const handlerArrow = () => {
console.log('this is:', this?.tag ?? '(lexical)');
};

handlerRegular.call(button); // 가상 디스패치
handlerArrow.call(button);
결과
this is: BUTTON
this is: (lexical)

DOM 이벤트 시스템은 핸들러를 handler.call(target, event) 로 호출하므로 일반 함수는 this = target. 화살표는 무시.


함정 모음

가독성

너무 짧게 쓰면 오히려 읽기 어렵다.

// 한 줄, 읽기 어려움
arr.reduce((a, b) => a.concat(b.map(c => c * 2)), []);

// 풀어쓰기
arr.reduce((acc, sublist) => {
  const doubled = sublist.map(x => x * 2);
  return acc.concat(doubled);
}, []);

화살표를 메서드로 클래스 필드에 쓸 때

class Btn {
  count = 0;
  onClick = () => this.count++; // this 는 인스턴스로 자동 bind
}

장점: addEventListener 에 그대로 넘겨도 this 가 살아있다. 단점: 인스턴스마다 함수 객체 생성 → 메모리 비용. prototype 메서드보다 비싸다.


참고

이 글의 용어 (5개)
[Javascript] 클로저javascript
정의 클로저 (Closure): 함수가 자신이 정의된 시점의 외부 변수들을 계속 참조할 수 있는 성질. 함수가 일급 객체 () 인 언어의 자연스러운 결과. 메커니즘은 + 함수 객…
[Javascript] functionjavascript
정의 은 JavaScript 에서 함수를 만드는 가장 기본적인 키워드. 호이스팅, 동적 바인딩, 객체, 호출 등 과 구분되는 고유한 특성을 가진다. 함수가 값 으로 다뤄지는 의 …
[Javascript] Lexical Environmentjavascript
정의 Lexical Environment (어휘적 환경) 는 ECMAScript 명세 (§9.1) 가 정의한 내부 객체. 함수가 호출될 때마다 새로 만들어지며, 이 호출의 변수들…
고차 함수javascript
- 고차함수란 하나 이상의 함수를 인자로 받거나 반환하는 함수를 말합니다. - 일급 함수라는 특성을 활용해 실제로 함수를 다루는 연산을 수행하는 함수입니다. 즉, 아래 조건 중 …
일급 함수javascript
- 함수를 값처럼 다루어 변수에 담거나 다른 함수의 인자로 전달하거나 함수의 반환값으로 사용할 수 있는 함수. 일급 함수의 이러한 특성을 활용하여 를 구현할 수 있습니다. - 함…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기