← JavaScript 강의 목록으로
🔒
함수형 · ES6
중급 · 선수: 11_배열_고차함수

12. 클로저와 스코프

함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.

클로저스코프렉시컬캡슐화
소요 시간
약 1.5시간
난이도
📊 중급
선수 조건
🎯 단원 11
결과물
함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.

이 강의에서 배우는 것

  • 1렉시컬 스코프(lexical scope) 의 의미를 설명한다.
  • 2클로저가 무엇인지, 언제 생성되는지 안다.
  • 3클로저로 비공개 상태(private state) 를 가진 카운터를 만든다.
  • 4모듈 패턴(IIFE) 으로 공개/비공개 멤버를 분리한다.
  • 5흔한 루프 변수 캡처 버그를 `let` 으로 해결한다.

소개

함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.

핵심 개념

1. 렉시컬 스코프

함수의 스코프는 **선언된 위치** 에 의해 정해집니다. 호출 위치가 아닙니다.

javascript
const x = 1;
function outer() {
  const y = 2;
  function inner() {
    return x + y; // outer/전역 변수 참조
  }
  return inner;
}

2. 클로저

함수가 자신이 만들어진 환경(변수)을 기억하는 현상이 클로저입니다. `outer()` 가 반환된 뒤에도 `inner` 는 `y` 를 사용할 수 있습니다.

javascript
const fn = outer();
fn(); // 3

3. 비공개 상태: 카운터

클로저로 외부에서 직접 접근할 수 없는 상태를 만들 수 있습니다.

javascript
function makeCounter() {
  let count = 0;
  return {
    inc() { count += 1; return count; },
    get() { return count; },
  };
}
const c = makeCounter();
c.inc(); c.inc(); c.get(); // 2

4. 모듈 패턴 (IIFE)

즉시 실행 함수 표현식으로 한 번만 실행되는 스코프를 만들고, 공개할 멤버만 반환합니다.

javascript
const Logger = (() => {
  let level = "info";
  function setLevel(l) { level = l; }
  function log(msg) { console.log(`[${level}] ${msg}`); }
  return { setLevel, log };
})();

핵심 예제

파일다루는 내용
`01_scope.js`블록/함수 스코프, let/const/var
`02_closure.js`클로저 기본
`03_counter.js`비공개 상태 카운터
`04_module_pattern.js`IIFE 모듈 패턴

src/01_scope.js

javascript
/**
 * 스코프
 */
"use strict";

const g = "global";

function outer() {
  const o = "outer";
  if (true) {
    const b = "block";
    console.log(g, o, b);
  }
  // console.log(b); // ReferenceError
}

outer();

// let 은 블록 스코프
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log("i =", i), 0);
}

src/02_closure.js

javascript
/**
 * 클로저 기본
 */
"use strict";

function makeGreeter(greeting) {
  return function (name) {
    return `${greeting}, ${name}!`;
  };
}

const hi = makeGreeter("Hi");
const hello = makeGreeter("Hello");
console.log(hi("Sun"));
console.log(hello("Moon"));

src/03_counter.js

javascript
/**
 * 비공개 상태 카운터
 */
"use strict";

function makeCounter(start = 0) {
  let count = start;
  return {
    inc() { return ++count; },
    dec() { return --count; },
    get() { return count; },
  };
}

const c = makeCounter(10);
c.inc();
c.inc();
c.dec();
console.log("count =", c.get()); // 11

src/04_module_pattern.js

javascript
/**
 * IIFE 모듈 패턴
 */
"use strict";

const Logger = (() => {
  let level = "info";
  function setLevel(l) { level = l; }
  function log(msg) { console.log(`[${level}] ${msg}`); }
  return { setLevel, log };
})();

Logger.log("start");
Logger.setLevel("warn");
Logger.log("careful");

자주 하는 실수

  1. `var` 로 선언한 루프 변수를 setTimeout 콜백에서 잘못 캡처한다 → `let` 사용.
  2. 클로저가 메모리를 잡고 있어 GC 가 안 됨을 잊는다 (대량 데이터 주의).
  3. 클로저 변수를 외부에서 직접 바꾸려 한다 (불가능).
  4. `function` 선언 호이스팅과 표현식 호이스팅 차이를 혼동.
  5. 동일 이름의 외부 변수와 내부 변수 섀도잉으로 혼란.

FAQ

Q1. 클로저는 언제 만들어지나요?

함수가 정의되는 순간 외부 환경을 참조하면 클로저가 만들어집니다.

Q2. 클로저는 성능에 나쁜가요?

일반적으로 무시할 수준입니다. 다만 큰 객체를 잡고 있으면 메모리 누수 가능.

Q3. let 과 const 의 블록 스코프?

`{ }` 단위로 유효 범위가 제한됩니다.

과제

  • `homework_01.js` — `makeAdder(n)` 을 만들어 더하기 클로저를 반환하세요.
  • `homework_02.js` — 비공개 잔액을 가진 `makeAccount` 를 작성하세요.

homework/README.md

## homework_01.js `makeAdder(n)` 을 작성하세요. 호출하면 `x` 를 받아 `x + n` 을 반환하는 함수를 돌려줍니다.

## homework_02.js `makeAccount(initial)` 을 작성하세요.

  • `deposit(amount)`, `withdraw(amount)`, `balance()` 를 노출
  • 잔액은 외부에서 직접 접근 불가

homework/answer/homework_01.js

javascript
/**
 * 과제 1: makeAdder
 */
"use strict";

/**
 * @param {number} n
 */
function makeAdder(n) {
  return (x) => x + n;
}

const add5 = makeAdder(5);
console.log(add5(3));  // 8
console.log(add5(10)); // 15

homework/answer/homework_02.js

javascript
/**
 * 과제 2: 비공개 잔액 계좌
 */
"use strict";

/**
 * @param {number} initial
 */
function makeAccount(initial) {
  let balance = initial;
  return {
    deposit(amount) { balance += amount; },
    withdraw(amount) {
      if (amount > balance) throw new Error("insufficient");
      balance -= amount;
    },
    balance() { return balance; },
  };
}

const acc = makeAccount(1000);
acc.deposit(500);
acc.withdraw(300);
console.log(acc.balance()); // 1200

다음 단원

[13_클래스](../13_클래스/) — class 문법으로 OOP.

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗