C
JavaScript/함수/Lesson 09

스코프 + 클로저 — *변수가 사는 영역*

1시간·theory
이 챕터
3/4

스코프 + 클로저 — *변수가 사는 영역*

🎯 이 lesson 을 읽고 나면

이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.

  • ✅ Lexical Scope + Closure 정의 + 코드 예시
  • ✅ Closure 로 private 변수 (카운터·캐시) 만들기
  • ✅ var 의 함수 스코프 함정과 let 으로 해결

학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.

스코프 — *어디서 변수에 접근 가능한가*

핵심 한 줄

스코프 (Scope) = 변수가 접근 가능한 범위. JS 의 스코프는 3종류: 전역·함수·블록.

3가지 스코프

javascript
// 전역 스코프 — 어디서나 접근
const globalVar = "전역";

function outer() {
    // 함수 스코프 — outer 안에서만
    const funcVar = "함수";

    if (true) {
        // 블록 스코프 — if 안에서만
        let blockVar = "블록";
        const alsoBlock = "여기도";

        // var 는 함수 스코프 (블록 무시)
        var varVar = "var 는 함수 스코프";
    }

    console.log(funcVar);     // ✅
    console.log(blockVar);    // ❌ ReferenceError
    console.log(varVar);      // ✅ var 는 if 밖에서도 접근
}
  • let·const — 블록 스코프 ({} 안)
  • var — 함수 스코프 (또 다른 var 의 함정)
  • 선언 키워드 없음 — 전역 (절대 X, 사고 원인)

스코프 체인 — 바깥쪽 찾아 올라가기

javascript
// 🌍 전역 스코프
const a = "전역";

function outer() {
    // 📦 outer 함수 스코프 (전역의 안쪽)
    const b = "outer";

    function inner() {
        // 🎯 inner 함수 스코프 (outer 의 안쪽)
        const c = "inner";

        console.log(c);   // "inner"  ← ① 내 스코프에 c 있음 → 바로 사용
        console.log(b);   // "outer"  ← ② 내 스코프에 b 없음 → outer 까지 올라가 발견
        console.log(a);   // "전역"   ← ③ outer 에도 a 없음 → 전역까지 올라가 발견
    }
    inner();   // 🚀 inner 실행 — 위 3줄 console.log 출력
}

outer();   // ▶️ 실행 시작! — 이 호출이 없으면 outer 도 inner 도 영영 실행 X

// 📤 콘솔 출력 (위에서 아래로):
//   inner
//   outer
//   전역

JS 가 변수를 찾을 때:
1. 현재 스코프
2. 외부 함수 스코프 (있다면)
3. 전역 스코프

연쇄적인 탐색 이 스코프 체인.

클로저 (Closure) — 함수가 자기 환경을 기억

한 줄 정의: 함수가 자기를 만든 곳의 변수 를 계속 들고 다니는 것. 비유로 — 🐷 저금통(count) 을 들고 다니는 🤚 손(반환된 함수).

javascript
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 1️⃣ 저금통 만드는 공장 (makeCounter)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
function makeCounter() {
    // 🐷 새 저금통 만들기 (안에 동전 0개)
    let count = 0;

    // 🤚 동전 넣는 "손" 을 만들어서 돌려줌
    return function () {
        count++;          // 저금통에 동전 1개 추가
        return count;     // 현재 동전 개수 알려줌
    };
    // ↑ 이 함수가 위의 count 변수를 "기억" 한 채로 밖으로 나감!
    //   → makeCounter 가 끝나도 count 는 사라지지 않음 (이게 클로저!)
}

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 2️⃣ 저금통 받기
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const counter = makeCounter();
//    ↑          ↑
//    │          └─ 공장 가동 → 🐷 + 🤚 묶음 만들어서 출고
//    └─ 받은 묶음을 counter 변수에 저장
//       → counter 는 이제 "저금통에 동전 넣는 손" 역할!

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 3️⃣ 손으로 동전 넣기 (counter 호출)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
console.log(counter());   // 1   ← 🤚 동전 넣음 → 🐷 안: 1개
console.log(counter());   // 2   ← 또 넣음   → 🐷 안: 2개 (같은 저금통!)
console.log(counter());   // 3   ← 또또 넣음 → 🐷 안: 3개 (계속 쌓임!)

// 💡 핵심:
//   - 매번 새 저금통 만드는 게 아님!
//   - counter() 호출은 같은 저금통에 동전을 계속 쌓는 행위
//   - 그래서 1, 2, 3 으로 늘어남

// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// 4️⃣ 만약 새 저금통이 필요하면?
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
const counter2 = makeCounter();   // 🐷 새 저금통 + 🤚 새 손 (완전히 별개!)

console.log(counter2());   // 1   ← 새 저금통이라 다시 0부터 시작!
console.log(counter2());   // 2
console.log(counter());    // 4   ← 첫 저금통은 여전히 3에서 이어짐!

// 💡 counter 와 counter2 는 각자 다른 저금통을 들고 있음 → 서로 영향 X

🎬 단계별 정리

  • [1단계] makeCounter() 호출 — 🏭 공장이 🐷 저금통(count=0)을 만들고 그 저금통에 접근 가능한 🤚 손을 출고
  • [2단계] const counter = ... — 🤚🐷 묶음을 counter 변수에 저장. 공장(makeCounter)은 문 닫지만, counter 가 손을 잡고 있으니 저금통도 살아남음!
  • [3단계] counter() 호출할 때마다 — 🤚가 🐷에 동전 1개씩 추가하고 현재 동전 개수를 알려줌

🎯 클로저 = "내부 변수를 외부에서 안전하게 다루는 손잡이"

장점: ✅ 변수가 외부에 노출 안 됨 (보안) ✅ 호출 사이에 값이 유지됨 (상태 기억) ✅ 각 호출이 독립적으로 자기 환경을 가짐

클로저의 활용 — 프라이빗 변수

javascript
function makeBank(initialBalance) {
    let balance = initialBalance;     // 외부 직접 접근 X

    return {
        deposit(amount) { balance += amount; },
        withdraw(amount) {
            if (amount > balance) throw new Error("부족");
            balance -= amount;
        },
        getBalance() { return balance; }
    };
}

const account = makeBank(1000);
account.deposit(500);
account.getBalance();    // 1500
account.balance;         // undefined — 직접 접근 X

balance함수 내부 에 있어서 외부에서 못 봅니다. 정해진 메서드 (deposit·withdraw) 로만 조작. 캡슐화 의 JS 방식.

흔한 함정 — 반복문 + var

javascript
// ❌ var 의 함정
for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1000);
}
// 출력: 3, 3, 3 (모두 같은 i)

// ✅ let — 블록 스코프
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 1000);
}
// 출력: 0, 1, 2

var함수 스코프 라 모든 반복이 같은 i 공유. let블록 스코프 라 매 반복마다 새 i.

한 번 정리

  • 스코프 = 변수의 접근 범위
  • let·const블록 스코프 (예측 가능)
  • 클로저 = 함수가 자기 환경 기억
  • 프라이빗 데이터·캡슐화 에 활용

⚡ 직접 해보기 — 저금통 클로저

🐷 같은 저금통 vs 새 저금통. 클로저가 "자기 환경 기억" 한다는 의미를 직접 확인.
✏️ JS 코드
📟 콘솔 출력
▶ 실행 버튼을 눌러보세요
⚠️ 브라우저 샌드박스에서 실행 — console.log()만 지원, alert/fetch 불가

🤖 AI 에게 이렇게 요청해보세요

이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.

  • "이 코드의 var 를 전부 const/let 으로 바꿔줘"
  • "재할당 가능성을 분석해서 const 우선으로 정리해줘"
  • "이 코드에서 hoisting 문제 가능성 진단해줘"

왜 이게 토큰을 줄이나

개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.

스코프와 클로저 - JavaScript