🔍 JavaScript Scope & Closure 시뮬레이터

클로저와 스코프 체인을 통해 변수 캡처 메커니즘을 시각적으로 학습해보세요

0
현재 Count

⏳ 실행 중인 setTimeout0

실행 중인 setTimeout이 없습니다

📋 실행 로그0

버튼을 클릭하면 실행 로그가 여기에 표시됩니다

🚨 문제 시연: 빠르게 버튼 누르기

실험해보세요: "올바른 방법 사용" 체크박스를 해제하고 "1초 후 증가" 버튼을 빠르게 여러 번 클릭해보세요!

예상: 클릭한 만큼 증가 | 실제: 1만 증가하는 문제 발생

❌ 잘못된 방법

setTimeout 콜백에서 캡처된 count 값을 사용하면, 모든 콜백이 동일한 값을 참조하게 됩니다.

✅ 올바른 방법

함수형 업데이트를 사용하면 최신 상태를 기반으로 올바르게 증가합니다.

📚 학습 가이드: JavaScript Scope & Closure

실제 코드와 함께 클로저와 스코프의 작동 원리를 이해해보세요

❌ 문제가 있는 코드

const delayedIncrement = () => {
  const currentCount = count; // 현재 값 캡처
  
  setTimeout(() => {
    // 잘못된 방법: 캡처된 값 사용
    const newValue = currentCount + 1;
    setCount(newValue);
  }, 1000);
};

// 빠르게 3번 클릭하면:
// 모든 setTimeout이 동일한 count 값을 캡처
// 결과: 모두 같은 값으로 업데이트 (1만 증가)

✅ 올바른 해결 코드

const delayedIncrement = () => {
  setTimeout(() => {
    // 올바른 방법: 함수형 업데이트 사용
    setCount(prev => prev + 1);
  }, 1000);
};

// 빠르게 3번 클릭하면:
// 각 setTimeout이 실행될 때마다 
// 최신 상태를 기반으로 업데이트
// 결과: 정확히 3만큼 증가

🧠 핵심 개념 이해

1. 클로저 (Closure)

함수가 생성될 때 외부 변수에 대한 참조를 "기억"하는 메커니즘입니다. setTimeout 콜백은 클로저를 통해 count 변수를 캡처합니다.

2. 변수 캡처

setTimeout이 생성되는 순간의 count 값이 캡처되어, 나중에 실행될 때도 그 값을 사용하게 됩니다.

3. 함수형 업데이트

setCount(prev => prev + 1)을 사용하면 실행 시점의 최신 상태를 받아서 업데이트하므로 클로저 문제를 해결할 수 있습니다.

4. 스코프 체인

JavaScript는 변수를 찾을 때 현재 스코프부터 시작해서 외부 스코프로 올라가며 검색합니다.

💡 클로저 패턴을 사용하는 이유

🔒 1. 데이터 캡슐화

외부에서 직접 접근할 수 없는 프라이빗 변수를 만들 수 있습니다.

function createCounter() {
  let count = 0; // 프라이빗 변수
  return {
    increment: () => ++count,
    getCount: () => count
  };
}

🏭 2. 팩토리 함수

설정값을 기억하는 맞춤형 함수를 생성할 수 있습니다.

function createMultiplier(factor) {
  return (num) => num * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);

🔧 3. 모듈 패턴

네임스페이스를 만들고 전역 변수 오염을 방지할 수 있습니다.

const MyModule = (() => {
  let privateVar = 'hidden';
  return {
    publicMethod: () => privateVar,
    setPrivate: (val) => privateVar = val
  };
})();

⚡ 4. 콜백 컨텍스트 유지

이벤트 핸들러나 비동기 함수에서 특정 값을 기억할 수 있습니다.

buttons.forEach((btn, index) => {
  btn.onClick = () => {
    console.log(`Button ${index} clicked`);
  };
});

🌟 실제 개발에서의 클로저 활용 예시

디바운스 함수

검색 입력이나 리사이즈 이벤트에서 자주 사용됩니다.

function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

const debouncedSearch = debounce(searchAPI, 300);

메모이제이션

계산 결과를 캐싱하여 성능을 최적화할 수 있습니다.

function memoize(func) {
  const cache = {};
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache[key]) {
      return cache[key];
    }
    const result = func.apply(this, args);
    cache[key] = result;
    return result;
  };
}

const memoizedFib = memoize(fibonacci);

💡 주의사항

  • • 클로저는 메모리를 계속 참조하므로 메모리 누수에 주의해야 합니다.
  • • 순환 참조를 만들지 않도록 조심해야 합니다.
  • • 과도한 클로저 사용은 성능에 영향을 줄 수 있습니다.
  • • 디버깅이 어려울 수 있으므로 명확한 네이밍이 중요합니다.
🧑‍💻

1nnovator 김민성

JavaScript 학습 센터 개발자

기술 블로그Interactive JavaScript Learning Platform

🌟 이 프로젝트가 도움이 되셨다면 블로그에서 더 많은 개발 이야기를 확인해보세요!

🤖이 페이지는 생성형 AI의 도움을 받아 제작되었습니다.