클로저(Closure)
중요도: ⭐⭐⭐⭐⭐
JavaScript의 핵심 개념으로, 함수형 프로그래밍과 모듈 패턴의 기초입니다.
클로저(Closure)는 함수가 선언될 당시의 렉시컬 환경(Lexical Environment)을 기억하여, 함수가 스코프 밖에서 실행될 때에도 그 환경에 접근할 수 있게 하는 개념입니다.
클로저의 기본 개념
function outerFunction(x) {
// 외부 함수의 변수
const outerVariable = x;
// 내부 함수 (클로저)
function innerFunction(y) {
// 외부 함수의 변수에 접근 가능
console.log(outerVariable + y);
}
return innerFunction;
}
const myFunction = outerFunction(10);
myFunction(5); // 15
// outerFunction이 실행 완료되어도
// innerFunction은 outerVariable에 접근 가능클로저의 특징
1. 외부 함수의 변수에 접근할 수 있습니다
function createCounter() {
let count = 0;
return function () {
return ++count;
};
}
const counter1 = createCounter();
const counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (독립적인 스코프)2. 외부 함수가 종료된 후에도 외부 함수의 변수에 접근 가능합니다
function greetingMaker(name) {
const greeting = "Hello, ";
return function (message) {
return greeting + name + "! " + message;
};
}
const greetJohn = greetingMaker("John");
// greetingMaker 함수는 이미 실행 완료되었지만
// 반환된 함수는 여전히 name과 greeting에 접근 가능
console.log(greetJohn("How are you?")); // "Hello, John! How are you?"3. 데이터 프라이버시를 제공합니다
function bankAccount(initialBalance) {
let balance = initialBalance; // private 변수
return {
// public 메서드들
deposit: function (amount) {
balance += amount;
return balance;
},
withdraw: function (amount) {
if (amount <= balance) {
balance -= amount;
return balance;
} else {
return "Insufficient funds";
}
},
getBalance: function () {
return balance;
},
};
}
const myAccount = bankAccount(1000);
console.log(myAccount.getBalance()); // 1000
console.log(myAccount.deposit(500)); // 1500
console.log(myAccount.withdraw(200)); // 1300
// balance 변수에 직접 접근할 수 없음
console.log(myAccount.balance); // undefined클로저의 활용
1. 데이터 은닉과 캡슐화
const userModule = (function () {
// private 변수
let users = [];
let currentId = 1;
// public API 반환
return {
addUser: function (name, email) {
const user = {
id: currentId++,
name: name,
email: email,
};
users.push(user);
return user;
},
getUser: function (id) {
return users.find((user) => user.id === id);
},
getAllUsers: function () {
// 원본 배열의 복사본 반환 (데이터 보호)
return users.slice();
},
};
})();
userModule.addUser("John", "john@example.com");
console.log(userModule.getAllUsers()); // [{ id: 1, name: "John", email: "john@example.com" }]
console.log(userModule.users); // undefined (직접 접근 불가)2. 모듈 패턴 구현
const calculator = (function () {
let result = 0; // private 상태
return {
add: function (x) {
result += x;
return this;
},
subtract: function (x) {
result -= x;
return this;
},
multiply: function (x) {
result *= x;
return this;
},
divide: function (x) {
if (x !== 0) {
result /= x;
}
return this;
},
getResult: function () {
return result;
},
reset: function () {
result = 0;
return this;
},
};
})();
// 메서드 체이닝 가능
calculator.add(10).multiply(2).subtract(5).divide(3);
console.log(calculator.getResult()); // 53. 콜백 함수에서 상태 유지
function setupEventHandlers() {
for (let i = 0; i < 3; i++) {
// 클로저를 사용하여 각 반복에서 i 값을 유지
setTimeout(function () {
console.log(`Timer ${i} finished`);
}, 1000 * i);
}
}
// var를 사용할 경우의 문제
function problematicSetup() {
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(`Timer ${i} finished`); // 모두 "Timer 3 finished" 출력
}, 1000 * i);
}
}
// 즉시실행함수로 해결
function fixedSetup() {
for (var i = 0; i < 3; i++) {
(function (index) {
setTimeout(function () {
console.log(`Timer ${index} finished`);
}, 1000 * index);
})(i);
}
}4. 함수형 프로그래밍에서의 커링(Currying)
// 클로저를 활용한 커링
function multiply(a) {
return function (b) {
return a * b;
};
}
const multiplyByTwo = multiply(2);
const multiplyByThree = multiply(3);
console.log(multiplyByTwo(5)); // 10
console.log(multiplyByThree(4)); // 12
// 더 복잡한 커링 예시
function createFormatter(prefix, suffix) {
return function (content) {
return prefix + content + suffix;
};
}
const htmlBold = createFormatter("<b>", "</b>");
const parentheses = createFormatter("(", ")");
console.log(htmlBold("Important")); // "<b>Important</b>"
console.log(parentheses("Note")); // "(Note)"클로저의 메모리 관리
메모리 누수 주의사항
// ❌ 메모리 누수 위험
function createLeak() {
const largeData = new Array(1000000).fill("data");
return function () {
// largeData를 참조하지 않지만 클로저로 인해 메모리에 유지됨
return "Hello";
};
}
// ✅ 메모리 최적화
function createOptimized() {
const largeData = new Array(1000000).fill("data");
const summary = largeData.length; // 필요한 정보만 추출
return function () {
return `Data length: ${summary}`;
};
}React에서의 클로저 활용
// React Hook에서 클로저
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
// 클로저를 활용한 메서드들
const increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
const decrement = useCallback(() => {
setCount((prev) => prev - 1);
}, []);
const reset = useCallback(() => {
setCount(initialValue);
}, [initialValue]);
return { count, increment, decrement, reset };
}
// 사용
function Counter() {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}클로저 디버깅 팁
클로저 디버깅
- Chrome DevTools의 Scope 패널 활용
- console.dir() 함수로 클로저 변수 확인
- 메모리 탭에서 메모리 누수 모니터링
function debugClosure() {
let secret = "hidden value";
function inner() {
console.log("Inner function");
debugger; // 여기서 Scope 패널 확인
}
return inner;
}
const fn = debugClosure();
fn();면접 팁
클로저에 대해 질문받을 때는 개념 설명과 함께 실무에서 어떻게 활용했는지 구체적인 예시를 들어 설명하세요. 모듈 패턴, 이벤트 핸들러, React Hook 등에서의 활용 경험을 언급하면 좋습니다. 또한 메모리 누수 가능성과 해결 방법도 함께 설명할 수 있어야 합니다.
Edit on GitHub
Last updated on