[Javascript] Arrow Function
정의
화살표 함수 (arrow function) 는 ES2015 (ES6) 에서 도입된 함수 표현식. () => {} 모양. 더 짧은 문법뿐 아니라 this 가 lexical 로 바인딩 되는 본질적 차이가 있다.
function (일반 함수) 와의 차이가 핵심.
문법 변형 (실행 결과로 확인)
// 인자 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
객체 리터럴 반환 시 {} 가 함수 본문 블록으로 해석되지 않도록 반드시 괄호로 감싸야 한다.
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 개념에서 자라난다.
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() 은 단순 호출이라 this 가 undefined.
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 가 없다
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 호출 불가
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. 호이스팅 안 됨
// 함수 선언은 호이스팅됨
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 시대)
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 를 하지 않았다면:
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)Counter 의 this (인스턴스) 가 콜백 안에서는 Window 가 되어 의도한 카운터가 동작하지 않는다.
.bind() 도 한 방법이었다
ES5 의 Function.prototype.bind 도 같은 목적으로 쓰였다.
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
...bind 는 self 보다 조금 더 명시적이지만 매번 새 함수 객체를 만들어 메모리 부담이 있고, 중첩 콜백에서는 .bind(this).bind(this) 같이 보기 흉해졌다.
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 시절의 흔한 패턴
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. 배열 메서드의 콜백
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 ]
152. 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);
})();
언제 화살표 함수를 피해야 하는가
객체 메서드
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 여야 할 때)
// 가상 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
- - 함수를 값처럼 다루어 변수에 담거나 다른 함수의 인자로 전달하거나 함수의 반환값으로 사용할 수 있는 함수. 일급 함수의 이러한 특성을 활용하여 를 구현할 수 있습니다. - 함…
💬 댓글