화살표 함수의 간결한 문법과 this 바인딩 차이를 정확히 이해한다. 렉시컬 스코프와 클로저의 동작 원리를 익히고, 클로저를 실무에서 활용하는 패턴을 배운다.
화살표 함수 — 간결함 그 이상
화살표 함수(Arrow Function)는 ES6에서 도입된 함수 표현 방식입니다. 단순히 짧게 쓰는 문법이 아니라, this 바인딩이 완전히 다르게 동작한다는 핵심 차이가 있습니다.
화살표 함수 문법
// 기본 비교
const add = function(a, b) { return a + b; }; // 일반 함수
const add = (a, b) => a + b; // 화살표 함수
// 매개변수 규칙
() => "값" // 매개변수 없음
x => x * 2 // 매개변수 1개 → 괄호 생략 가능
(x, y) => x + y // 매개변수 2개 이상 → 괄호 필수
// 본문 규칙
x => x * 2 // 단일 표현식 → return 생략
x => { return x * 2; } // 블록 본문 → return 명시
x => ({ id: x, value: x * 2 }) // 객체 반환 → 소괄호로 감싸기!
// 잘못된 예
x => { id: x } // 에러! — {}가 블록으로 해석됨
x => ({ id: x }) // 올바름 — ()로 객체임을 표시
this 바인딩 — 핵심 차이
일반 함수는 호출 방식에 따라 this가 동적으로 결정됩니다. 화살표 함수는 선언된 위치의 this를 그대로 사용(렉시컬 this)합니다.
// 일반 함수의 this 문제
const timer = {
count: 0,
start: function() {
setInterval(function() {
this.count++; // this가 window/undefined (콜백에서 this 변경됨)
console.log(this.count); // NaN
}, 1000);
}
};
// 화살표 함수로 해결
const timer2 = {
count: 0,
start: function() {
setInterval(() => {
this.count++; // this가 timer2 (화살표 함수는 상위 this 상속)
console.log(this.count); // 1, 2, 3, ...
}, 1000);
}
};
// 메서드에는 화살표 함수 쓰지 마세요
const obj = {
name: "Kim",
greet: () => {
console.log(this.name); // undefined — 화살표 함수는 obj의 this를 갖지 않음
}
};
obj.greet(); // undefined
// 메서드는 일반 함수로
const obj2 = {
name: "Kim",
greet() { // 메서드 단축 표기
console.log(this.name); // "Kim"
}
};
스코프(Scope) — 변수의 유효 범위
// 전역 스코프
const globalVar = "전역";
function outer() {
const outerVar = "외부"; // 함수 스코프
if (true) {
const blockVar = "블록"; // 블록 스코프
console.log(globalVar); // 접근 가능
console.log(outerVar); // 접근 가능
console.log(blockVar); // 접근 가능
}
console.log(globalVar); // 접근 가능
console.log(outerVar); // 접근 가능
// console.log(blockVar); // ReferenceError!
}
// console.log(outerVar); // ReferenceError!
스코프 체인
변수를 찾을 때 현재 스코프부터 시작해 바깥 스코프로 순서대로 탐색합니다.
const x = "전역";
function outer() {
const x = "외부";
function inner() {
const x = "내부";
console.log(x); // "내부" (가장 가까운 스코프)
}
function inner2() {
console.log(x); // "외부" (inner2에 없으면 outer로)
}
inner();
inner2();
}
outer();
클로저(Closure) — 자바스크립트의 핵심 개념
클로저는 함수가 자신이 선언된 스코프의 변수를 기억하고 접근할 수 있는 현상입니다. 함수가 반환된 후에도 외부 스코프 변수를 살려둡니다.
function makeCounter() {
let count = 0; // makeCounter의 지역 변수
return {
increment() { return ++count; },
decrement() { return --count; },
getCount() { return count; }
};
}
const counter = makeCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount()); // 1
// count는 외부에서 직접 접근 불가 (캡슐화!)
console.log(counter.count); // undefined
// 독립적인 카운터
const counter2 = makeCounter();
counter2.increment();
console.log(counter2.getCount()); // 1 (counter와 독립)
클로저 실무 활용 패턴
// 1. 함수 팩토리
function multiplier(factor) {
return (n) => n * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
[1, 2, 3].map(double); // [2, 4, 6]
// 2. 메모이제이션 (캐싱)
function memoize(fn) {
const cache = {};
return function(n) {
if (n in cache) return cache[n];
cache[n] = fn(n);
return cache[n];
};
}
const expensiveCalc = memoize(function(n) {
// 복잡한 계산...
return n * n;
});
expensiveCalc(10); // 계산 후 캐시
expensiveCalc(10); // 캐시에서 즉시 반환
// 3. 이벤트 핸들러에서 데이터 캡처
function attachClickHandlers(buttons) {
buttons.forEach((button, index) => {
button.addEventListener("click", () => {
console.log(`버튼 ${index} 클릭됨`);
// index가 클로저로 캡처됨
});
});
}
클로저 함정 — var와 루프
// 유명한 var + 클로저 버그
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 출력: 3, 3, 3 (예상: 0, 1, 2)
// var는 함수 스코프라 같은 i를 공유함
// 해결 1: let 사용 (블록 스코프)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 출력: 0, 1, 2 ✅
// 해결 2: IIFE로 스코프 생성 (구식 방법)
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
관련 검색어 · 화살표 함수 this, 자바스크립트 클로저, 렉시컬 스코프, 클로저 활용 패턴, 스코프 체인, 메모이제이션