아래 코드를 봅시다.
01 const outer = function () {
02 let a = 1;
03 const inner = function () {
04 return ++a;
05 };
06 return inner;
07 };
08 const outer2 = outer();
09 console.log(outer2()); // 2
10 console.log(outer2()); // 3
outer 함수는 inner 함수를 반환합니다. 그러면 outer2 변수는 outer의 실행 결과인 inner 함수를 참조하게 될 것입니다. 이후 9번째 줄에서 outer2를 호출하면 inner 함수가 실행되겠죠.
inner 함수의 environmentRecord에는 담길 정보가 없습니다. outerEnvironmentReference에는 outer 함수의 LexicalEnvironment가 담기겠죠. 따라서 변수 a에 접근하려면 스코프 체이닝에 따라 outer에서 선언한 변수 a에 접근해서 1만큼 증가시킵니다.
그런데 이상한 점이 있습니다. inner 함수가 실행되는 시점에는 outer 함수의 실행 컨텍스트가 종료된 상태인데 어떻게 outer 함수의 LexicalEnvironment에 접근할 수 있는 걸까요? 이는 가비지 컬렉터의 동작 방식 때문입니다. 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있으면 그 값은 수집 대상에 포함시키지 않습니다.
outer 함수는 실행 종료 시점에 inner 함수를 반환합니다. 외부 함수인 outer의 실행이 종료되더라도 내부 함수인 inner 함수는 언젠가 outer2를 실행함으로써 호출될 가능성이 열린것이죠. 언젠가 inner 함수의 실행 컨텍스트가 활성화되면 outerEnvironmentReference가 outer 함수의 LexicalEnvironment를 필요로 할 것이므로 수집 대상에서 제외됩니다. 그 덕에 inner 함수가 이 변수에 접근할 수 있는 것이죠.
이것이 클로저입니다. 요약하면 클로저는 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수를 외부로 전달할 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상(가비지컬렉팅되지 않는 현상)을 말합니다.