우당탕탕

JavaScript 클로저, 실전에서 헷갈렸던 부분 단계별로 정리했어요 본문

언어/JavaScript

JavaScript 클로저, 실전에서 헷갈렸던 부분 단계별로 정리했어요

모찌모찝 2026. 9. 24. 11:13

JavaScript 클로저를 구현하다가 생각보다 여러 번 막혔던 경험이 있어요. 처음에는 개념만 알고 있었는데, 실전 코드에 적용하려니 의도한 대로 동작하지 않아서 한참 삽질했거든요.

이 글에서는 제가 실제 프로젝트에서 클로저를 어떻게 단계별로 세팅하고 활용했는지, 그 과정에서 겪었던 핵심 헷갈림 포인트와 해결 방법을 자세히 설명할게요. “클로저가 뭔지 대충 아는데, 실전에서 어떻게 써야 할지 모르겠다” 하시는 분들께 도움이 될 거예요.

1단계: 클로저의 기본 구조부터 익히기

사실 이 부분이 제일 기본인데도 의외로 헷갈리더라고요. 클로저는 "함수가 생성될 때 그 함수가 만들어진 렉시컬 환경을 기억하는" 거잖아요? 저도 처음에는 그냥 함수 안에서 변수를 참조하는 걸로만 이해했는데, 실제로 동작하는 예제를 하나씩 만들면서 감을 잡았어요.

// 클로저 기본 예제
function makeCounter() {
  let count = 0; // 외부 함수의 변수
  return function() {
    count += 1;
    return count;
  }
}

const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2

이 예제에서 중요한 포인트는 내부 함수가 외부 함수의 변수를 계속 기억하고 있다는 것이에요. 저는 이게 왜 가능한지 한참 봤는데, 결국 "클로저는 변수의 실제 값이 아니라 그 변수의 환경을 기억한다"는 사실을 이해하니 이해가 됐어요.

JavaScript 클로저 실전에서 헷갈렸던 사례 관련 이미지

JavaScript 클로저 실전에서 헷갈렸던 사례 관련 정보

2단계: 클로저를 실전 코드에 적용해보기

그런데 여기서 많이 틀리는 부분이 있었는데, 클로저 사용 시 변수를 어떻게 선언하고 관리하느냐에 따라 결과가 달라진다는 거였어요. 실제 프로젝트에서 이벤트 핸들러를 등록할 때 이런 문제가 자주 발생하더라고요.

예를 들어, 반복문 안에서 클로저를 쓰면 전혀 예상 못 한 값이 참조되는 순간이 있는데요. 저는 이 부분에서 한참 혼란스러웠습니다.

// 헷갈렸던 반복문 클로저 문제
for (var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 3 3 3 이 출력됨
  }, 100);
}

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

// 해결법 2: 즉시 실행 함수 활용
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // 0 1 2 정상 출력
    }, 100);
  })(i);
}

이걸 겪으면서 저는 var 변수는 함수 레벨 스코프라서 반복문에서 클로저를 쓸 땐 별도의 스코프가 필요하다는 점을 확실히 알았어요. 반면에 let은 블록 스코프라 걱정 없이 쓸 수 있더라고요.

JavaScript 클로저 실전에서 헷갈렸던 사례 직접 정리한 자료

JavaScript 클로저 실전에서 헷갈렸던 사례 관련 정보

3단계: 클로저로 상태 관리할 때 유용한 패턴 저는 이렇게 썼어요

실제 앱 개발할 때는 클로저로 상태를 감싸는 경우가 많잖아요? 저는 상태 변경 함수를 내부에 두고 외부에는 상태를 직접 변경하지 못하게 하는 패턴을 썼어요. 이게 의도치 않은 상태 변화를 막기 좋아서 꽤 편리하더라고요.

// 상태 은닉과 클로저 활용
function createToggle() {
  let isOn = false;
  return {
    toggle() {
      isOn = !isOn;
      return isOn;
    },
    getState() {
      return isOn;
    }
  };
}

const toggle = createToggle();
console.log(toggle.getState()); // false
console.log(toggle.toggle());   // true
console.log(toggle.getState()); // true

이 패턴에서는 내부 상태 isOn이 외부에서 접근 불가능한 상태로 은닉되기 때문에, 내부 함수가 상태를 안전하게 관리해 줘요. 저는 이걸 통해 상태 혼란 없이 UI 동기화를 수월하게 할 수 있었어요.

JavaScript 클로저 실전에서 헷갈렸던 사례 참고 사진

JavaScript 클로저 실전에서 헷갈렸던 사례 관련 정보

4단계: 클로저 때문에 메모리가 늘어나는 이슈, 이렇게 관리했어요

그런데 여기서 또 문제였던 게 클로저가 가리키는 외부 변수들이 메모리를 계속 참조해서 GC가 안 되는 경우가 있다는 점이더라고요. 특히 SPA에서 오래 실행되는 앱에선 메모리 누수가 크게 느껴지기 쉽거든요.

저는 이 문제를 다음과 같은 절차로 해결했어요.

  • 1. 클로저 내부에서 참조하는 변수나 객체가 필요한 시점에만 존재하게 설계
  • 2. 사용하지 않는 함수나 이벤트 핸들러는 명시적으로 null 또는 undefined로 할당해 해제
  • 3. 가능하면 클로저 내 상태를 최소화하여 불필요한 데이터 참조 자제
// 메모리 누수 방지 예시
function setup() {
  let bigData = new Array(1000000).fill('data');

  function process() {
    console.log(bigData.length);
  }

  process();

  // bigData 해제
  bigData = null;
}

setup();

이렇게 변수 참조를 해제하지 않으면 함수가 클로저로 계속 참조해서 메모리가 반환되지 않더라고요. 저는 이걸 알게 되면서 SPA 성능에도 크게 도움이 됐어요.

5단계: 클로저 사용 시 자주 하는 실수와 피하는 방법

이 부분도 제가 가장 많이 실패했던 지점인데요, 클로저 안에 this를 예상과 다르게 사용하는 경우가 생각보다 많아요. 저는 이 문제 때문에 함수가 의도한 객체를 참조하지 않을 때가 있었거든요.

실제로 이벤트 핸들러 안에서 this가 바뀌면서 참조 객체가 달라져서 디버깅하느라 고생했는데, 해결책은 화살표 함수로 this 바인딩을 고정하거나, bind 메서드를 명확히 쓰는 것이었어요.

// this 문제 예시
const obj = {
  count: 0,
  increment() {
    setTimeout(function() {
      // 여기서 this는 전역 객체(window)라 count를 못 참조함
      console.log(this.count); // undefined
    }, 100);
  }
};

obj.increment();

// 해결법: 화살표 함수 사용
const objFixed = {
  count: 0,
  increment() {
    setTimeout(() => {
      console.log(this.count); // 0, 정상 참조
    }, 100);
  }
};

objFixed.increment();

저는 이 부분을 놓쳤다가 문제가 꽤 심각해져서 결국 코드 전체를 손봤던 기억이 있어요. 혹시 비슷한 문제 겪는 분이라면 저처럼 꼭 확인해 보시길 바랍니다.

자주 물어보시는 것들

Q. 클로저로 만든 변수는 왜 밖에서 직접 변경이 안 되나요?

A. 클로저가 감싼 변수는 외부에서 직접 접근할 수 없고, 내부 함수만 접근 가능해요. 그래서 의도하지 않은 외부 변경을 방지하는 캡슐화 역할을 합니다. 외부에서 변경하려면 내부에 변경용 함수(메서드)를 별도로 만들어 제공해야 해요.

Q. 클로저 때문에 메모리 누수가 난다고 하는데, 꼭 문제가 될까요?

A. 꼭 그렇진 않지만, 장시간 실행되는 앱이나 대용량 데이터를 참조하는 클로저가 많으면 메모리 사용량이 증가할 수 있어요. 그래서 꼭 필요할 때만 클로저를 만들고, 사용 후에는 변수 참조를 해제하는 걸 추천해요.

Q. 화살표 함수가 클로저나 this 문제를 완전히 해결해 주나요?

A. 대부분 상황에서 화살표 함수는 this 바인딩 문제를 간단히 해결해 주지만, 화살표 함수는 자체적으로 arguments 객체가 없고, 생성자 함수로 사용할 수 없으니 상황에 맞게 사용해야 해요.

직접 따라 하면서 클로저 단계별로 세팅해 보니, 개념보다 실제 절차와 코드가 얼마나 중요한지 깨달았어요. 여러분도 단계별로 차근차근 클로저 적용해 보시면 생각보다 쉽게 감 잡을 수 있을 거예요. 이 글에서 알려드린 순서대로 차근차근 실습해 보세요.

Comments