JavaScript 클로저 완전 정복 – 초보자를 위한 함수 기억하기 비법 (55자)
# 서론 – “왜 함수가 기억을 할까?”
프로그래밍을 처음 시작한 사람이라면 “함수가 호출될 때마다 새로운 메모리를 할당한다”는 설명을 들어본 적이 있을 겁니다. 그런데 JavaScript에서는 함수가 자신이 선언됐던 환경을 기억하고, 그 기억을 바탕으로 동작한다는 사실, 알고 계셨나요?
이 현상을 제대로 이해하지 못하면 비동기 처리, 콜백, 모듈화 등 실무에서 마주치는 수많은 버그에 허덕이게 됩니다. 이번 글에서는 클로저(Closure) 라는 개념을 쉽게 풀어내고, 실제 예제와 함께 함수가 어떻게 기억을 하는지를 단계별로 살펴보겠습니다.
키워드: JavaScript 클로저, 함수 스코프, 프로그래밍 기초
# 1️⃣ 클로저란 무엇인가? – 기본 개념 정리
# 1-1. 스코프와 렉시컬 스코프
스코프(scope) : 변수와 함수가 접근 가능한 범위
렉시컬(Lexical) 스코프 : 함수가 정의된 위치에 따라 스코프가 결정
JavaScript는 정적 스코프를 사용합니다. 즉, 함수가 어디서 호출되었는가가 아니라 어디서 정의되었는가에 따라 변수에 접근할 수 있는 범위가 정해집니다.
# 1-2. 클로저 정의
클로저는 함수가 선언된 렉시컬 환경(외부 변수들)을 함께 기억하고, 그 환경에 접근할 수 있는 함수와 그 환경의 조합을 말합니다.
쉽게 말해, 함수가 살아있는 동안 자신이 선언된 스코프를 “포획”하고, 그 포획된 스코프를 통해 변수에 접근하는 것이 클로저입니다.
# 1-3. 왜 클로저가 필요한가?
데이터 은닉 : 외부에서 직접 접근할 수 없는 비공개 변수 생성
상태 유지 : 함수 호출 사이에 값을 유지(예: 카운터)
함수 팩토리 : 매개변수에 따라 다른 동작을 하는 함수를 동적으로 생성
# 2️⃣ 클로저의 작동 원리 – 단계별 시각화
# 2-1. 실행 컨텍스트와 변수 환경 레코드
함수 선언 → 실행 컨텍스트가 생성되고, 변수 환경 레코드(Variable Environment Record) 가 만든다.
함수가 호출될 때마다 새로운 실행 컨텍스트가 스택에 쌓인다.
함수가 종료되면 실행 컨텍스트는 사라지지만, 클로저가 참조하고 있는 변수 환경 레코드는 가비지 컬렉션되지 않는다.
# 2-2. 예제: 간단한 카운터 만들기
javascript
function makeCounter() {
let count = 0; // 외부 변수
return function() { // 내부 함수 (클로저)
count += 1;
return count;
};
}
const counter = makeCounter(); // makeCounter 실행 → 내부 함수 반환
console.log(counter()); // 1
console.log(counter()); // 2
makeCounter실행이 끝나도count변수는 소멸되지 않는다.반환된 내부 함수는
count를 포획하고 있기 때문에, 매 호출 시 이전 값을 기억한다.
# 2-3. 클로저와 메모리 관리
클로저가 불필요하게 큰 객체를 포획하면 메모리 누수가 발생할 수 있다.
사용이 끝난 클로저는 null 로 할당하거나, 스코프를 제한해 메모리를 해제한다.
# 3️⃣ 실전 예제 – 비동기와 콜백에서 클로저 활용하기
# 3-1. 타이머와 클로저
javascript
function startTimer(seconds) {
let remaining = seconds;
const id = setInterval(function() {
remaining--;
console.log(`남은 시간: ${remaining}s`);
if (remaining <= 0) clearInterval(id);
}, 1000);
}
startTimer(5);
setInterval내부 콜백은remaining변수를 클로저로 포획한다.타이머가 진행되는 동안
remaining값은 지속적으로 업데이트된다.
# 3-2. 이벤트 리스너와 클로저
javascript
function attachClickHandler(btnId) {
let clickCount = 0;
document.getElementBy **getElementById**(btnId).addEventListener('click', function() {
clickCount++;
console.log(`버튼 클릭 횟수: ${clickCount}`);
});
}
attachClickHandler('myBtn');
- 클릭 이벤트 콜백이
clickCount를 기억하므로, 버튼을 누를 때마다 누적된 횟수를 출력한다.
# 3-3. 클로저를 이용한 모듈 패턴
javascript
const TodoModule = (function() {
let todos = []; // 비공개 변수
return {
add: function(item) { todos.push(item); },
list: function() { return [...todos]; }
};
})();
TodoModule.add('Learn JavaScript');
console.log(TodoModule.list()); // ['Learn JavaScript']
즉시 실행 함수(IIFE) 안에 비공개 데이터
todos를 두고, 외부에 공개된 메서드만 반환한다.이 패턴은 데이터 은닉과 API 제공을 동시에 만족한다.
# 4️⃣ 클로저 흔히 하는 실수와 해결법
| 흔한 실수 | 원인 | 해결 방법 |
|----------|------|-----------|
| 루프 안에서 클로저 사용 시 변수 공유 | var 로 선언된 변수는 함수 스코프라서 마지막 값만 기억 | let 사용하거나 즉시 실행 함수를 활용 |
| 메모리 누수 | 클로저가 큰 DOM 객체 등을 포획 | 필요 없는 참조는 null 로 초기화, 혹은 스코프 최소화 |
| 디버깅 어려움 | 클로저 내부 변수는 콘솔에 바로 보이지 않음 | 브라우저 DevTools의 Closure Scope 확인 기능 활용 |
javascript
// var 로 인한 문제 예시
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 3, 3, 3
}, 100);
}
// let 로 해결
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}
# 5️⃣ 클로저와 최신 JavaScript (ES6+)
arrow function 은
this바인딩이 lexical이지만, 클로저와는 별개이다.module (ES6
import/export) 은 내부적으로 클로저를 활용해 프라이빗 스코프를 만든다.async/await 와 Promise 도 콜백 대신 클로저를 이용해 비동기 흐름을 관리한다.
javascript
// async 함수 안에서 클로저 활용
async function fetchData(url) {
const response = await fetch(url);
const data = await response.json();
return function() {
console.log('받은 데이터:', data); // data는 클로저에 포획
};
}
const logData = await fetchData('/api/items');
logData(); // 콘솔에 데이터 출력
# 결론 – 지금 바로 클로저를 활용해 보세요!
클로저는 JavaScript 프로그래밍 기초 중에서도 가장 강력하면서도 헷갈리기 쉬운 개념입니다.
하지만 위에서 살펴본 스코프 이해 → 변수 환경 포획 → 실전 적용 순서를 따라가면, 어느새 데이터 은닉, 상태 유지, 비동기 제어 등 다양한 상황에서 클로저를 자연스럽게 활용하고 있을 겁니다.
# 📌 행동 유도
직접 실습 – 오늘 배운 카운터 예제를 콘솔에 입력하고 동작을 확인해 보세요.
코드 리팩터링 – 기존 프로젝트에 콜백이 많은 부분을 클로저 기반 모듈로 바꿔 보세요.
블로그 공유 – 이 글이 도움이 되었다면 SNS에 공유하고, #JavaScript클로저 해시태그와 함께 의견을 남겨 주세요!
클로저를 제대로 이해하고 활용한다면, 복잡한 로직도 깔끔하게 관리하고 버그를 크게 줄일 수 있습니다. 이제 여러분도 JavaScript의 비밀스러운 기억력을 손에 넣으세요! 🚀
Comments
Join the discussionNo comments yet. Be the first to comment.