클로저
제리2026.03.18
loading ...
| 항목 | 내용 |
| 주차 | 2주차 |
| 날짜 | 2026-03-18 |
| 주제 | 클로저 |
| 발표자 | 이세비 |
이 주제를 한 문장으로 설명하면?
한 줄 요약:
"클로저는 함수가 선언된 시점의 렉시컬 환경을 기억해, 외부 함수의 실행이 끝난 후에도 그 스코프의 변수에 접근할 수 있는 메커니즘이다."
관련 키워드 (마인드맵 형태로 나열):
[클로저 (Closure)]├── 전제 개념│ ├── 렉시컬 스코프 (Lexical Scope)│ ├── 실행 컨텍스트 (Execution Context)│ └── 스코프 체인 (Scope Chain)├── 클로저의 동작│ ├── 외부 변수 참조 유지│ ├── 자유 변수 (Free Variable)│ └── GC 수거 방지├── 활용 패턴│ ├── 데이터 은닉 (Private 변수)│ ├── 상태 유지 (카운터, 누적값)│ ├── 팩토리 함수│ └── once 패턴├── 주의사항│ ├── Stale Closure (useEffect / useCallback)│ └── 메모리 누수└── 실무 연결├── React useState / useEffect / useCallback└── debounce / throttle
⚠️ 단순 복붙 금지! 반드시 자신의 말로 재작성할 것
개념 설명 (내 말로):
자바스크립트는 함수가 어디서 호출됐는지가 아니라 어디서 선언됐는지를 기준으로 상위 스코프를 결정한다. 함수가 선언되는 순간 상위 스코프가 정적으로 확정되며, 이후 호출 위치와 무관하게 그 스코프의 변수에 접근한다.
동작 원리 / 흐름:
실제 코드 예시:
// 호출 위치가 아닌 선언 위치 기준const value = 'outer';function printValue() {console.log(value); // 선언 위치(전역)의 value 참조}function wrapper() {
개념 설명 (내 말로):
내부 함수가 외부 함수의 변수를 참조할 때, 외부 함수의 실행이 끝나도 해당 변수가 GC에 수거되지 않고 내부 함수를 통해 계속 접근 가능한 상태가 된다. 이 내부 함수를 클로저라고 한다.
동작 원리 / 흐름:
실제 코드 예시:
function makeAdder(x) {// x는 makeAdder의 지역 변수return function(y) {return x + y; // 외부 함수의 x를 클로저로 참조};}const add5 = makeAdder(
개념 설명 (내 말로):
클로저는 주로 세 가지 목적으로 활용된다. 1. 변수를 외부에서 직접 접근하지 못하도록 은닉할 때 2. 전역 변수 없이 함수 호출 간 상태를 유지할 때 3. 특정 값이 고정된 새 함수를 생성할 때(팩토리 함수)
동작 원리 / 흐름:
실제 코드 예시:
// 1️⃣ 데이터 은닉function createAccount(initialBalance) {let balance = initialBalance; // 외부 직접 접근 불가return {deposit(amount) { balance += amount; return balance; },
이 기술/개념이 왜 이런 방식으로 만들어졌는지 배경을 이해하자
클로저 vs 전역 변수 — 상태 유지 방법 비교
| 구분 | 전역 변수 | 클로저 |
| 접근 범위 | 코드 어디서든 접근 가능 | 반환된 함수를 통해서만 접근 |
| 데이터 보호 | ❌ 외부에서 직접 수정 가능 | ✅ 외부 직접 접근 차단 |
| 네임스페이스 오염 | ❌ 전역 오염 위험 | ✅ 스코프 내부에 격리 |
| 상태 유지 | ✅ | ✅ |
일반 함수 vs 클로저를 반환하는 함수 — 변수 수명 비교
| 구분 | 일반 함수 | 클로저 반환 함수 |
| 실행 후 지역 변수 | GC 수거 대상 | 내부 함수가 참조하는 한 유지 |
| 호출 간 상태 기억 | ❌ 매번 초기화 | ✅ 유지 |
| 데이터 은닉 | ❌ | ✅ |
실제 프로젝트에서 마주칠 수 있는 상황
① React useState 개념 모델
함수형 컴포넌트는 렌더링마다 함수가 재실행되지만, useState는 클로저를 통해 이전 상태를 유지한다.
// 실제 구현과 다르지만, 클로저 관점의 개념 모델function useState(initialVal) {let _state = initialVal;const getState = () => _state;const setState = (next) =>
② Stale Closure — useEffect 의존성 누락
// ❌ 문제: time이 항상 0으로 고정됨function Timer() {const [time, setTime] = useState(0);useEffect(() => {const id = setInterval
③ Stale Closure — useCallback 의존성 누락
// ❌ 문제: query가 항상 초기값 ''를 참조function SearchBox() {const [query, setQuery] = useState('');const search = useCallback(() => {fetchResults
ESLint react-hooks/exhaustive-deps 규칙이 의존성 누락을 경고하는 이유가 바로 이 Stale Closure 문제다.
④ debounce
function debounce(fn, delay) {let timerId = null;return function(...args) {clearTimeout(timerId);timerId = setTimeout(()
면접관 앞에서 말하듯이 작성. 읽는 용도가 아니라 말하는 연습을 위한 섹션
[도입 - 핵심 정의 먼저]"클로저는 함수가 선언된 시점의 렉시컬 환경을 기억해,외부 함수 실행이 끝난 후에도 그 스코프의 변수에 접근할 수 있는 메커니즘입니다."[부연 - 왜/어떻게]"자바스크립트는 함수 선언 시 상위 스코프 정보를 내부에 저장합니다.내부 함수가 외부 변수를 참조하고 있으면 외부 함수가 종료돼도해당 변수는 GC에 수거되지 않고 유지됩니다."[마무리 - 실무 연결]"실무에서는 주로 세 가지로 활용합니다.데이터를 외부에서 직접 수정하지 못하게 은닉하거나,전역 변수 없이 함수 호출 간 상태를 유지하거나,특정 값을 고정한 새 함수를 만드는 팩토리 패턴에 씁니다.React의 useState도 클로저로 상태를 유지하며,useEffect의 의존성 배열을 잘못 관리하면 Stale Closure 버그가 발생합니다."
면접관이 바쁠 때, 딱 1분 안에 설명하는 버전
클로저는 함수가 선언될 당시의 렉시컬 환경을 기억하여, 외부 스코프의 변수에 계속 접근할 수 있는 함수입니다. 외부 함수가 종료된 이후에도 내부 함수가 해당 변수를 참조하고 있다면, 그 변수는 가비지 컬렉션의 대상이 되지 않고 유지됩니다. 이러한 특성을 활용해 데이터 은닉, 상태 유지, 팩토리 함수 구현 등에 사용되며, React Hooks 역시 이 원리를 기반으로 동작합니다.
각 질문에 대해 STAR 구조 혹은 두berlingo 두berlingo 두 문장 요약으로 작성
A: 클로저는 함수가 선언될 당시의 렉시컬 환경을 기억하여, 외부 스코프의 변수에 계속 접근할 수 있는 함수입니다. 내부 함수가 외부 함수의 변수를 참조할 경우, 외부 함수의 실행이 종료된 이후에도 해당 변수는 가비지 컬렉션의 대상이 되지 않고 유지되며, 내부 함수를 통해 계속 접근할 수 있는 상태를 의미합니다.
A: 세 가지 상황에서 주로 활용합니다. 첫째, 데이터 은닉입니다. 변수를 클로저 내부에 캡슐화해 외부에서 직접 수정하지 못하도록 하고, 공개된 메서드로만 접근하게 할 수 있습니다. 둘째, 상태 유지입니다. 전역 변수 없이 함수 호출 간 값을 보존할 수 있어 네임스페이스 오염을 방지합니다. 셋째, 팩토리 함수입니다. 인자를 클로저로 고정해 특정 동작이 미리 설정된 함수를 만들 수 있습니다.
A: 렉시컬 스코프가 클로저를 가능하게 하는 전제 조건입니다. 자바스크립트는 함수 선언 시 상위 스코프가 정적으로 결정되어 함수 내부에 기록됩니다. 덕분에 외부 함수가 종료된 이후에도 내부 함수가 외부 변수를 참조할 수 있습니다.
for (var i = 0; i < 3; i++) {setTimeout(() => console.log(i), 0);}
A: 3 3 3이 출력됩니다. var는 함수 스코프이므로 i는 루프 블록이 아닌 전역에 하나만 존재합니다. setTimeout 콜백이 실행될 시점에는 루프가 이미 끝나 i === 3인 상태이고, 세 콜백 모두 동일한 i를 클로저로 공유하기 때문입니다.해결 방법은 두 가지입니다.
// ① let: 블록 스코프로 반복마다 독립적인 i 생성for (let i = 0; i < 3;
A: 네, Stale Closure(낡은 클로저) 문제가 대표적입니다. useEffect의 의존성 배열이 비어있으면 effect 내부 함수가 최초 실행 시점의 state·props를 클로저로 캡처한 채 갱신되지 않습니다. 예를 들어 setInterval 안에서 setState(count + 1)을 쓰면 count가 항상 초기값 0으로 고정됩니다. 해결 방법은 두 가지입니다.
- 함수형 업데이트: setState(prev => prev + 1) — React가 최신 state를 prev로 주입해줍니다.
- 의존성 배열 명시: 해당 변수를 배열에 추가하면 변수가 바뀔 때마다 effect가 재실행됩니다.
ESLint react-hooks/exhaustive-deps 규칙이 존재하는 이유가 이 Stale Closure 문제를 방지하기 위해서입니다.
A: 메모리 누수(Memory Leak)에 주의해야 합니다. 클로저가 외부 변수를 참조하는 한 그 변수는 GC 수거 대상이 되지 않습니다. 이벤트 핸들러를 클로저로 등록하고 해제하지 않으면, 컴포넌트가 언마운트된 후에도 참조가 유지되어 메모리가 점유됩니다.// ❌ 메모리 누수 위험function setup() {const data = new Array(100000).fill(0);
개념을 몸으로 익히기 위한 미니 실습
// ① private 변수를 가진 스택 구현function createStack() {const items = []; // 외부 직접 접근 불가return {push(item) { items.push(item); },
공부하고 나서 진짜 내 머릿속에 남은 것들만 정리
| 헷갈린 것 | 알고 보니 |
| 클로저가 특별한 문법인 줄 알았다 | 별도 문법이 없다. 함수가 선언 시 스코프를 자동으로 기록하므로 사실상 모든 함수가 클로저다 |
| for + var에서 클로저가 각각 다른 i를 기억할 줄 알았다 | var는 블록 스코프가 없어 루프 전체에서 i 하나를 공유한다. let을 쓰면 반복마다 독립적인 i가 생성된다 |
| useEffect 빈 배열이면 안전한 줄 알았다 | 빈 배열은 최초 실행 시점의 값만 캡처한다. 이후 state가 변해도 effect 내부 함수는 초기값을 참조한다 (Stale Closure) |
| 자료 | 링크 | 추천 이유 |
| MDN - 클로저 (한국어) | https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures | 공식 문서, 렉시컬 스코프부터 실용 패턴까지 포괄 |
| MDN Glossary - Closure | https://developer.mozilla.org/en-US/docs/Glossary/Closure | 간결한 정의와 카운터 예제 |
| javascript.info - 클로저 | https://ko.javascript.info/closure | 렉시컬 환경 동작 원리 상세 설명 |
| React 공식 문서 - useState | https://react.dev/reference/react/useState | 함수형 업데이트 패턴 |
| React 공식 문서 - useEffect |
React에서는 useEffect cleanup 함수로 이벤트 구독이나 타이머를 반드시 정리해야 합니다.
| 의존성 배열과 Stale Closure |
| React 공식 문서 - useCallback | https://react.dev/reference/react/useCallback | 의존성 누락 시 동작 |