JavaScript 클로저: 코드를 구동하는 조용한 메커니즘

발행: (2025년 12월 28일 오후 02:26 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

나는 내가 인정하고 싶지 않을 정도로 오래 동안 클로저를 피했다.

특별히 피한 건 아니다. 나는 그냥 “클로저는 자신의 렉시컬 스코프를 기억하는 함수다”라는 흔한 문장을 반복하면서 이해했다고 스스로에게 말해왔을 뿐이다. 그 문장은 설득력 있게 들리지만, 그것만으로는 충분하지 않다.

클로저는 그것을 외워야 할 개념으로만 생각하는 것을 멈추고, 코드가 실제로 보여주는 동작으로 바라볼 때 비로소 의미가 있다.

이 글은 정의를 나열하는 것이 아니다. 실제 JavaScript 코드에서 클로저가 어떻게 나타나는지, 그리고 초보자들이 생각하는 것보다 왜 훨씬 더 중요한지를 설명하려는 시도이다.

함수를 작성할 때 클로저가 생성되지 않는다

클로저는 함수가 선언될 때 생성되지 않는다. 함수가 실행되고, 그렇지 않으면 사라질 외부 스코프의 변수를 계속 참조하게 될 때 비로소 생성된다.

function createCounter() {
  let count = 0;

  return function () {
    count++;
    return count;
  };
}

const counter = createCounter();

언뜻 보기에 countcreateCounter가 실행을 마치자마자 사라져야 할 것처럼 보인다. 하지만 실제로는 그렇지 않다. 내부 함수는 여전히 그 변수에 접근할 수 있다.

왜일까? 자바스크립트는 값을 함수 안으로 복사하지 않기 때문이다. 함수가 생성된 환경에 대한 참조를 유지한다. 그 환경, 즉 렉시컬 환경을 우리가 흔히 클로저라고 부른다.

클로저는 문법이 아니라 메모리와 관련이 있다

가장 유용한 사고 전환 중 하나는 클로저가 근본적으로 메모리 모델이며, 문법 기능이 아니라는 것을 깨닫는 것이다. JavaScript 엔진은 변수들을 무언가가 여전히 접근할 수 있는 한 살아 있게 유지한다.

위 예시에서:

  • count는 여전히 접근 가능하다
  • 따라서 메모리에 남아 있다
  • 따라서 함수 호출 사이에 값이 지속된다

그게 전부다. 마법은 없다. 이렇게 클로저를 바라보면, 많은 혼란스러운 동작이 갑자기 예측 가능해진다.

클로저가 처음에 혼란스러운 이유

클로저는 많은 사람들이 무의식적으로 따르는 직관적인 규칙을 깨뜨립니다:

“함수가 끝나면 그 안에 있던 모든 것이 사라진다.”

그 규칙은 대부분 맞지만—때때로 그렇지 않을 때도 있습니다. 클로저는 그 규칙을 증명하는 예외입니다. 클로저는 누가 아직도 어떤 것에 대한 참조를 가지고 있는지에 대해 생각하게 만듭니다. 이것이 루프 안에서 클로저가 흔히 버그의 원인이 되는 이유이기도 합니다.

for (var i = 0; i  {
    console.log(i);
  }, 1000);
}

사람들은 0, 1, 2가 나올 거라 기대합니다. 하지만 3, 3, 3이 나옵니다. 이는 JavaScript가 깨졌기 때문이 아니라, 세 함수 모두 같은 변수를 클로저로 잡고 있기 때문이며, 서로 다른 세 값이 아니라 같은 변수를 가리키기 때문입니다. 클로저를 이해하면 이 동작이 더 이상 놀랍지 않게 됩니다.

클로저는 어디에나 존재합니다, 눈치채지 못해도

다음과 같은 경험이 있다면:

  • React에서 useState를 사용한 적이 있다
  • 콜백 함수를 작성한 적이 있다
  • setTimeout 또는 setInterval을 사용한 적이 있다
  • 이벤트 리스너를 다뤄본 적이 있다
  • 커링이나 부분 적용을 구현한 적이 있다

이미 클로저를 사용하고 있는 것입니다.

프레임워크는 클로저에 크게 의존합니다. 클로저를 사용하면 전역 변수를 쓰지 않고도 상태를 유지할 수 있기 때문에 설계상 큰 장점이 됩니다. 예를 들어 React 훅은 렌더링 사이에 값을 “기억”하기 위해 클로저를 활용하면서도 함수는 겉보기에는 순수하게 유지됩니다.

클로저는 클래스를 사용하지 않고 캡슐화를 가능하게 합니다

ES6 클래스가 대중화되기 전, 클로저는 JavaScript에서 프라이빗 상태를 만들 수 있는 주요 방법 중 하나였습니다. 오늘날에도 많은 개발자들이 간단한 캡슐화를 위해 클래스보다 클로저를 선호합니다:

function createBankAccount() {
  let balance = 0;

  return {
    deposit(amount) {
      balance += amount;
    },
    getBalance() {
      return balance;
    }
  };
}

여기서 balance는 외부에서 완전히 접근할 수 없습니다. 프라이버시를 위한 키워드가 존재하지 않으며, 프라이버시는 클로저에서 자연스럽게 발생합니다. 이 패턴은 여전히 놀라울 정도로 잘 작동합니다.

단점: 클로저가 객체를 너무 오래 유지할 수 있음

클로저는 강력하지만 비용이 없습니다. 클로저가 참조를 유지하기 때문에, 특히 브라우저나 서버와 같은 장시간 실행되는 애플리케이션에서 큰 객체가 필요 이상으로 오래 메모리에 남아 있을 수 있습니다. 클로저에 의해 발생하는 메모리 누수는 미묘하고 종종 오해받습니다. 문제는 클로저 자체가 아니라, 당신이 참조하는 것이 살아남는다는 사실을 잊는 데 있습니다.

클로저를 이해하면 가비지 컬렉션 규칙을 외우는 것보다 메모리 사용을 훨씬 더 잘 판단할 수 있습니다.

클로저를 설명하는 더 나은 방법 (한 문장으로)

클로저는 함수가 생성될 때 존재하던 변수들과의 실시간 연결을 유지할 때 발생하는 현상입니다.

그 문장은 시적이지는 않지만 정확합니다.

더 읽고 이해를 심화하기

If you want to go deeper, here are some genuinely useful directions to explore:

  • MDN’s explanation of lexical environments and execution contexts
  • Articles that explore JavaScript engine internals (V8 in particular)
  • Functional programming discussions on closures in Scheme and Lisp
  • Academic papers on lexical scoping and environment models (arXiv is useful here)

Closures are not unique to JavaScript. Understanding them well makes learning other languages easier.

최종 생각

클로저는 한 번만 배우는 것이 아닙니다. JavaScript를 오래 작성할수록 점점 더 명확히 눈에 띄게 되는 것입니다. 어느 순간, 클로저는 신비로운 면접 주제가 아니라 생각 없이 의존하게 되는 조용하고 믿음직한 도구가 됩니다. 이것이 바로 여러분이 마침내 클로저를 이해했음을 나타내는 신호입니다.

Back to Blog

관련 글

더 보기 »

React에서 간단한 Carousel/Slider 만들기

캐러셀 또는 슬라이더는 이미지나 콘텐츠를 하나씩 표시하는 훌륭한 방법입니다. 버튼을 사용하여 이를 탐색할 수 있습니다. 아래는 간단한 구현...