개발자/개발

[프론트엔드] 자바스크립트 클로저

naheesu 2025. 3. 12. 16:45

📌 JavaScript 클로저(Closure) 자세히 알아보기

1️⃣ 클로저란?

클로저(Closure)는 "함수와 그 함수가 선언된 렉시컬 환경을 함께 기억하는 구조".
즉, 함수가 실행된 후에도 해당 함수의 지역 변수에 접근할 수 있는 개념.

📌 쉽게 말하면
"함수 안에서 선언된 함수가, 바깥 함수의 변수들을 기억하고 사용할 수 있도록 하는 것"


2️⃣ 클로저를 이해하는 기본 개념

🔹 렉시컬 스코프 (Lexical Scope)

자바스크립트는 렉시컬 스코프(정적 스코프) 를 사용해!
즉, 함수가 선언된 위치에서 변수를 찾는다. (실행 위치가 아니라 선언 위치 기준)

function outer() {
  let outerVar = "나는 바깥 변수!";
  
  function inner() {
    console.log(outerVar); // "나는 바깥 변수!"
  }
  
  inner();
}
outer();

✅ inner() 함수는 outerVar 변수를 자신의 내부에서 선언하지 않았지만
outerVar가 선언된 위치(렉시컬 환경)를 기억하고 접근 가능


3️⃣ 클로저의 핵심 기능

✅ 1. 데이터 은닉 (Encapsulation)

클로저를 이용하면 외부에서 직접 접근할 수 없는 변수를 만들 수 있어.
즉, 변수를 외부에서 변경하지 못하게 보호하는 것!

function createCounter() {
  let count = 0; // 외부에서 접근 불가능한 변수

  return {
    increase: () => count++, 
    decrease: () => count--,
    getCount: () => count
  };
}

const counter = createCounter();
console.log(counter.getCount()); // 0
counter.increase();
console.log(counter.getCount()); // 1
counter.decrease();
console.log(counter.getCount()); // 0

✅ count 변수는 createCounter() 내부에 있지만,
✅ increase, decrease, getCount를 통해 간접적으로 접근 가능
외부에서 count를 직접 변경할 수 없음! (데이터 은닉)


✅ 2. 상태 유지 (State Maintenance)

클로저를 사용하면, 함수가 실행된 후에도 내부 변수를 유지할 수 있어

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

const add5 = makeAdder(5);  // x = 5 유지
console.log(add5(2)); // 7
console.log(add5(10)); // 15

const add10 = makeAdder(10); // x = 10 유지
console.log(add10(5)); // 15

✅ add5는 makeAdder(5) 호출 시 x = 5를 기억
✅ add10은 makeAdder(10) 호출 시 x = 10을 기억
✅ 각각 독립적인 상태를 유지하면서 동작


✅ 3. 비동기 처리 및 콜백 함수에서 유용

클로저는 이벤트 리스너, setTimeout, 비동기 처리에서 매우 유용해!

function delayedMessage(message, delay) {
  setTimeout(() => {
    console.log(message);
  }, delay);
}

delayedMessage("3초 후 출력!", 3000);

클로저 덕분에 message 변수값이 유지되고,
✅ 3초 뒤에 "3초 후 출력!"이 출력됨


4️⃣ 클로저의 메모리 관리

❗ 클로저가 참조하는 변수만 메모리에 유지됨

  • 함수 실행이 끝나도 클로저가 있는 한 해당 변수는 메모리에 남아있음.
  • 하지만 더 이상 참조되지 않으면 GC(가비지 컬렉터)에 의해 자동 해제됨
function outer() {
  let largeData = new Array(1000000).fill("📦"); // 메모리를 많이 차지하는 데이터

  return function inner() {
    console.log(largeData[0]);
  };
}

const myFunction = outer(); // 클로저 생성
myFunction(); // "📦"

// 클로저를 더 이상 참조하지 않으면 메모리 해제됨
myFunction = null;

✅ myFunction = null; 하면 largeData도 더 이상 참조되지 않아 메모리에서 해제됨.


5️⃣ 클로저를 활용한 실전 예제

📌 1. 이벤트 리스너에서 사용

function attachEventHandlers() {
  let count = 0;

  document.getElementById("btn").addEventListener("click", () => {
    count++;
    console.log(`버튼 클릭 횟수: ${count}`);
  });
}
attachEventHandlers();

클로저 덕분에 count 변수가 버튼 클릭 횟수를 유지함.


📌 2. 한 번만 실행되는 함수 (once function)

function once(fn) {
  let executed = false;
  return function(...args) {
    if (!executed) {
      executed = true;
      return fn(...args);
    }
  };
}

const logOnce = once(() => console.log("한 번만 실행됨!"));

logOnce(); // "한 번만 실행됨!"
logOnce(); // 실행 안 됨
logOnce(); // 실행 안 됨

✅ 클로저를 이용해 한 번만 실행되는 함수를 만들 수 있음.


🎯 정리

✔️ 클로저(Closure)란?
➡ 함수와 함수가 선언된 렉시컬 환경을 기억하는 구조

✔️ 클로저의 주요 기능
1️⃣ 데이터 은닉: 외부에서 직접 접근할 수 없는 변수를 만들 수 있음.
2️⃣ 상태 유지: 함수 실행 후에도 변수 값을 유지할 수 있음.
3️⃣ 콜백과 비동기 처리: 클로저는 콜백 함수, 이벤트 리스너에서 유용하게 사용됨.

✔️ 클로저의 메모리 관리

  • 클로저가 참조하는 변수는 메모리에 유지됨
  • 더 이상 참조되지 않으면 GC(가비지 컬렉션)에서 해제됨

 


 

🎯 클로저를 사용하면 좋은 이유 (장점 정리)

✔ 1. 데이터 은닉 & 보안성 향상

  • 변수를 외부에서 직접 접근할 수 없도록 보호
  • private 변수 역할을 할 수 있음

✔ 2. 상태 유지 (Stateful Function)

  • 이전 상태를 기억하면서 활용할 수 있음
  • 이벤트 핸들러, 반복문에서 유용함

✔ 3. 메모리 효율적 사용

  • 필요한 변수만 유지하면서 불필요한 메모리는 자동 해제
  • GC(가비지 컬렉션) 에 의해 참조되지 않는 변수는 제거됨

✔ 4. 비동기 코드에서 활용 가능

  • setTimeout, setInterval 같은 비동기 처리에서 변수 유지에 유리함

✔ 5. 함수형 프로그래밍에 적합

  • 고차 함수(Higher-Order Function) 를 만들 때 활용 가능
  • 콜백 함수, 이벤트 리스너, 커링(Currying) 등에 활용

🔥 결론

"클로저를 사용하면 변수를 외부에서 보호하면서도 유지할 수 있다!"
이런 특징 덕분에 보안성, 상태 관리, 비동기 처리, 고차 함수 활용 등에서 큰 장점을 가짐.

실무에서도 자주 사용되니 꼭 익혀두자! 🚀