Learning
레슨 9 / 12·5개 토픽

클로저와 스코프

스코프(Scope)의 종류

JavaScript의 스코프는 변수에 접근할 수 있는 범위를 의미합니다. var는 함수 스코프, letconst는 블록 스코프를 가집니다. 렉시컬 스코프(Lexical Scope)란 함수가 정의된 위치를 기준으로 상위 스코프가 결정되는 것을 말합니다.

javascript
// 블록 스코프 vs 함수 스코프
if (true) {
  var varX = 10;   // 함수 스코프 — 블록 밖에서도 접근 가능
  let letY = 20;   // 블록 스코프 — 블록 안에서만 접근 가능
  const constZ = 30;
}
console.log(varX);   // 10
// console.log(letY);  // ReferenceError
// console.log(constZ); // ReferenceError

// 렉시컬 스코프 — 함수가 정의된 위치 기준
const outer = "전역";

function outerFn() {
  const middle = "중간";

  function innerFn() {
    const inner = "내부";
    console.log(outer);  // "전역" — 렉시컬 스코프 체인
    console.log(middle); // "중간"
    console.log(inner);  // "내부"
  }

  innerFn();
}

outerFn();

클로저(Closure)란?

클로저는 함수가 자신이 정의된 환경(렉시컬 환경)을 기억하는 것입니다. 외부 함수가 실행을 마친 후에도, 내부 함수는 외부 함수의 변수에 접근할 수 있습니다. 이를 통해 데이터를 캡슐화하고 상태를 유지할 수 있습니다.

javascript
// 카운터 — 클로저로 private 상태 만들기
function createCounter() {
  let count = 0;  // 외부에서 직접 접근 불가

  return {
    increment() { return ++count; },
    decrement() { return --count; },
    getCount()  { return count; },
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1
console.log(counter.getCount());  // 1
// console.log(count);  // ReferenceError — 외부에서 접근 불가

// private 변수 패턴
function createUser(name) {
  let _password = "";  // 클로저로 보호

  return {
    getName()       { return name; },
    setPassword(pw) { _password = pw; },
    checkPassword(pw) { return _password === pw; },
  };
}

const user = createUser("김철수");
user.setPassword("1234");
console.log(user.checkPassword("1234")); // true
console.log(user.checkPassword("0000")); // false

클로저 실전 활용

javascript
// 이벤트 핸들러에서 클로저 활용
function setupButtons() {
  const colors = ["red", "green", "blue"];

  colors.forEach(function(color) {
    const btn = document.createElement("button");
    btn.textContent = color;
    // color 변수가 클로저로 캡처됨
    btn.addEventListener("click", function() {
      document.body.style.backgroundColor = color;
    });
    document.body.appendChild(btn);
  });
}

// 함수 팩토리 — 클로저로 설정값 기억
function createMultiplier(factor) {
  return function(number) {
    return number * factor;  // factor를 클로저로 기억
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5));  // 10
console.log(triple(5));  // 15

IIFE (즉시 실행 함수 표현식)

IIFE(Immediately Invoked Function Expression)는 정의와 동시에 실행되는 함수입니다. 전역 스코프를 오염시키지 않고 독립적인 스코프를 만들 때 사용합니다.

javascript
// 기본 IIFE 패턴
(function() {
  const secret = "IIFE 안에서만 접근 가능";
  console.log(secret);
})();
// console.log(secret);  // ReferenceError

// 값을 반환하는 IIFE
const module = (function() {
  let privateData = 0;

  return {
    getData() { return privateData; },
    setData(val) { privateData = val; },
  };
})();

module.setData(42);
console.log(module.getData()); // 42

클로저의 흔한 함정: 반복문과 변수

javascript
// 문제: var는 함수 스코프 → 모든 콜백이 같은 i를 참조
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);  // 3, 3, 3 (모두 3 출력)
  }, 100);
}

// 해결 1: let 사용 (블록 스코프)
for (let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);  // 0, 1, 2 (정상 출력)
  }, 100);
}

// 해결 2: IIFE로 각 반복마다 새 스코프 생성
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);  // 0, 1, 2
    }, 100);
  })(i);
}
💡

클로저는 메모리에 렉시컬 환경을 유지하므로, 불필요한 클로저는 메모리 누수를 일으킬 수 있습니다. 이벤트 리스너를 제거하거나 참조를 null로 설정하여 가비지 컬렉션이 되도록 해주세요.