logo

DowanKim

40. 클로저, 렉시컬 스코프

2026년 4월 14일

자바스크립트

자바스크립트가 왜 그토록 유연하고 강력한지, 그 비밀은 함수가 태어난 환경을 끝까지 기억하는 '의리'에 있습니다.


🏗️ 1. 렉시컬 환경: 엔진의 비밀 지도

자바스크립트 엔진은 코드가 실행될 때 내부적으로 렉시컬 환경이라는 객체를 만들어 관리합니다.

  • 환경 레코드 (Environment Record): 현재 블록 내의 모든 지역 변수가 저장되는 곳입니다.
  • 외부 참조 (Outer Reference): 현재 환경의 바깥쪽(부모) 렉시컬 환경을 가리키는 화살표입니다.

변수 검색의 규칙 (The Search)

함수 내부에서 변수를 찾을 때, 엔진은 "안쪽에서 바깥쪽으로" 탐색합니다.

  1. 현재 내부 렉시컬 환경을 뒤진다.
  2. 없으면 외부 참조를 따라 부모 환경으로 올라간다.
  3. 전역(Global) 환경까지 가서도 못 찾으면 에러를 낸다.

🔐 2. 클로저 (Closure): 기억을 잃지 않는 함수

클로저란 **"외부 변수를 기억하고 이 변수에 접근할 수 있는 함수"**를 말합니다. 자바스크립트의 모든 함수는 태어날 때 부모의 주소록을 복사해서 [[Environment]]라는 숨겨진 주머니에 넣고 다닙니다.

function makeCounter() { let count = 0; // 이 변수는 함수가 끝나도 사라지지 않습니다. return function() { return count++; // 외부 변수 count를 기억하고 있습니다. }; } let counter = makeCounter(); alert( counter() ); // 0 alert( counter() ); // 1

왜 사라지지 않을까? (Garbage Collection)

보통 함수 호출이 끝나면 그 안의 변수들은 메모리에서 지워집니다. 하지만 중첩 함수가 살아있는 한, 그 함수가 가리키는 외부 렉시컬 환경은 가비지 컬렉터의 청소 대상에서 제외됩니다. 누군가 자기를 부를 수도 있으니까요!


🎙️ 기술 면접 대비 (Interview Questions)

Q1. "클로저가 무엇인가요?" (가장 중요)

답변: 클로저는 함수와 그 함수가 선언된 렉시컬 환경의 조합입니다. 자바스크립트의 함수는 생성될 때 자신의 상위 스코프를 기억하며, 함수가 자신이 생성된 스코프 밖에서 실행될 때도 그 환경에 접근할 수 있게 해주는 메커니즘입니다. 모든 자바스크립트 함수는 본질적으로 클로저입니다.

Q2. 반복문 안에서 함수를 만들 때 발생하는 클로저 이슈와 해결책은? (중급)

답변: var를 쓰면 모든 함수가 동일한 전역 변수를 공유하게 되어 의도치 않은 결과를 냅니다. 해결책은 let을 사용하는 것입니다. let은 반복문이 돌 때마다 새로운 렉시컬 환경을 생성하므로, 각 함수가 자신만의 독립적인 값을 기억하게 됩니다.

Q3. 클로저의 단점은 무엇인가요? (심화)

답변: 클로저는 외부 환경을 참조하고 있으므로, 함수가 더 이상 필요하지 않은 상태에서도 메모리를 점유하게 됩니다. 대량의 클로저를 적절히 해제하지 않으면 메모리 누수(Memory Leak)가 발생할 수 있습니다. 따라서 사용이 끝난 클로저 변수에는 null을 할당해 참조를 끊어주는 것이 좋습니다.


🛠️ 실무 인사이트: AI-Native 개발과 캡슐화

소프트웨어 마에스트로나 카카오 테크 캠퍼스 같은 고난도 프로젝트를 진행하다 보면, 전역 오염을 막기 위해 데이터를 숨겨야(Encapsulation) 할 때가 많습니다. 이때 클로저는 클래스 없이도 프라이빗 변수를 만드는 가장 우아한 방법이 됩니다.

특히 V8 엔진은 성능을 위해 클로저가 사용하지 않는 변수는 알아서 메모리에서 제거하는 최적화를 수행합니다. 하지만 디버깅 시에는 이 때문에 ReferenceError를 만날 수도 있으니, 크롬 개발자 도구의 debugger를 활용할 때 주의해야 합니다.


클로저는 자바스크립트 학습자들에게 '통곡의 벽'이라 불릴 만큼 까다롭지만, 한 번 이해하면 자바스크립트라는 드라마의 주인공이 누구인지 명확히 알 수 있는 핵심 개념입니다.

클로저를 **"태어난 고향을 기억하는 함수"**라고 생각하고 접근해 봅시다.


🎒 1. 클로저(Closure)가 대체 뭐야?

클로저는 **"함수"**와 **"그 함수가 선언될 당시의 주변 환경(렉시컬 환경)"**의 조합입니다.

쉽게 비유하자면, 함수는 태어날 때 부모님이 챙겨준 **'비상식량 배낭'**을 메고 나옵니다. 함수가 고향(자신이 선언된 스코프)을 떠나 저 멀리 다른 코드에서 실행되더라도, 배낭 속에 든 비상식량(외부 변수)을 언제든 꺼내 먹을 수 있는 상태, 그게 바로 클로저입니다.


🧬 2. 왜 모든 함수가 사실상 클로저야?

자바스크립트 엔진은 함수를 만들 때, 그 함수가 어디서 만들어졌는지 절대 잊어버리지 않도록 **[[Environment]]**라는 숨겨진 주머니에 '고향 주소'를 저장합니다.

  • 글로벌 함수: 전역 환경을 기억합니다.
  • 중첩 함수: 자기를 만든 부모 함수의 환경을 기억합니다.

결국 자바스크립트의 모든 함수는 태어나는 순간 자신의 고향에 대한 참조를 갖게 되므로, 이론적으로는 모든 함수가 클로저인 셈입니다. (다만, 우리가 보통 '클로저'라고 부르는 상황은 함수가 자신이 태어난 곳을 벗어나 외부에서 호출될 때를 말합니다.)

] hidden property reference]


🛠️ 3. 이걸 왜, 어디에 쓰는 거야?

클로저는 단순히 이론이 아니라 실무에서 **'상태를 안전하게 보호'**하기 위해 사용합니다.

① 데이터 숨기기 (Encapsulation / Private Variables)

외부에서 아무나 내 변수를 수정하지 못하게 '캡슐' 안에 가두고 싶을 때 씁니다.

function createCounter() { let count = 0; // 외부에서 접근 불가! (Private) return function() { return ++count; // 클로저를 통해 count에 접근 }; } const counter = createCounter(); console.log(counter()); // 1 console.log(counter()); // 2 // count라는 변수는 오직 counter 함수를 통해서만 조작할 수 있습니다.

② 함수 공장 (Function Factories)

특정 상태를 유지하는 함수를 여러 개 찍어낼 때 유용합니다.

function makeMultiplier(x) { return function(y) { return x * y; // x는 클로저로 유지됨 }; } const double = makeMultiplier(2); const triple = makeMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15

🧪 4. 클로저의 동작 원리 (렉시컬 환경)

함수가 호출되면 엔진은 **렉시컬 환경(Lexical Environment)**을 만듭니다.

  1. 환경 레코드: 현재 함수 안의 변수들을 저장.
  2. 외부 참조: [[Environment]] 주머니에 들어있던 고향 주소를 연결.

변수를 찾을 때 "내 방에 없네? 거실(부모 스코프) 가보자. 거기도 없네? 마당(전역) 가보자!" 하는 식으로 꼬리에 꼬리를 물고 올라가는데, 이 연결 고리가 끊어지지 않고 유지되는 것이 클로저의 핵심입니다.


🎙️ 기술 면접용 핵심 요약

질문: 클로저에 대해 설명해 주세요.

답변: "클로저는 함수가 선언된 렉시컬 환경과의 조합입니다. 자바스크립트의 모든 함수는 생성될 때 자신의 상위 스코프를 [[Environment]] 프로퍼티에 저장하여 기억합니다. 이 덕분에 함수가 자신이 생성된 스코프 밖에서 실행되더라도 상위 스코프의 변수에 접근할 수 있게 됩니다. 주로 데이터 캡슐화나 상태 유지 등을 위해 사용됩니다."


💡 Tech Lead의 한 줄 인사이트

클로저는 강력하지만, 부모의 환경을 계속 붙잡고 있기 때문에 메모리를 점유한다는 단점이 있습니다. 수만 개의 클로저를 무분별하게 만들면 메모리 누수(Memory Leak)의 원인이 될 수 있으니, 사용이 끝난 클로저는 참조를 해제(null 할당 등)하는 습관을 들이는 것이 좋습니다.