“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다. 출처: MDN
단순히 정의만 봐서는 뭔말인지 이해하기가 어렵기 때문에 코드와 함께 살펴보도록 하겠습니다.
const x = 1;
function outerFunc() {
const x = 10;
const innerFunc = function() {
console.log(x)
};
return innerFunc;
}
const inner = outerFunc()
inner(); //10
위와 같은 코드는 콘솔에 어떤 값이 나올까요? x를 출력하는 것으로 보아 1 아니면 10 둘 중 하나가 찍힐 것으로 보입니다.
결과부터 말하자면 10이 콘솔에 찍히게 됩니다.
전역스코프에서 함수호출을 했는데 1이 아닌 10이 나온게 신기하지 않으신가요?
왜 이런 결과가 나오게 되었는지 천천히 살펴보도록 하겠습니다.
우선 구조부터 보면 outerFunc이 선언이 되어있고, 그 안에 변수 x가 선언되어 있으며 innerFunc이 표현식으로 작성되어 있습니다.
그리고 outerFunc은 중첩함수 innerFunc을 inner에게 반환하면서 생명주기를 마감하는 함수입니다. 즉, outerFunc이 종료가 되면 실행 컨텍스트 스택에서 제거가 됩니다.
const inner = outerFunc()
따라서 outerFunc의 지역변수인 x 또한 생명주기를 마감하게 되기 때문에 x에 접근할 방법이 없는 것처럼 보입니다.
하지만 코드를 실행해보면 여전히 x에 접근하고 있다는 것을 알 수 있습니다.
여기서 의문점이 생기는데요. 어떻게 생명주기가 마감된 x에 접근할 수 있었을까요?
해답은 바로 클로저에 있습니다.
앞의 예제와 함께 설명하자면 중첩함수 innerFunc가 이미 생명주기를 마감한 외부함수 outerFunc의 지역변수 x를 참조할 수 있다면 이 때 innerFunc을 클로저라고 합니다.
그러면 innterFunc이 클로저가 될 수 있었던 과정을 조금 더 상세하게 살펴보도록 하겠습니다.
위의 그림에서 outerFunc의 생명주기가 끝나게 되면 실행 컨텍스트에서 사라지게 됩니다.
이후 outerFunc는 inner에게 innerFunc을 반환하면서 생명주기가 끝나게 되기 때문에 innerFunc 객체를 참조하게 됩니다.
그림이 바뀌어가는 과정을 보면 outerFunc은 실행컨텍스트에서 사라졌지만 그 렉시컬 환경까지 사라지지는 않았습니다.
inner 는 이제 innerFunc 객체를 참조하고 있으며 inner 함수는 outer 함수의 렉시컬 환경을 참조하기 때문에 가비지 컬렉션의 대상이 되지 않습니다. 가비지 컬렉션은 단 하나라도 참조하는 객체가 존재할 경우 소멸의 대상에서 제외시킵니다.
따라서 inner는 outer 함수의 렉시컬 환경에 존재하는 x를 참조할 수 있게 됩니다.
비록 outerFunc의 생명주기가 끝남과 함게 실행 컨텍스트에서 제외되었지만 그 환경만큼은 여전히 남아있게 되며 중첩함수는 자신의 스코프 내에서 식별자를 찾지 못할 경우 상위 스코프에서 탐색을 하여 식별자를 참조하게 되게 됩니다.
바로 이것이 클로저 입니다.
위의 과정을 정리하자면
클로저는 자신이 생성되었을 때의 환경을 기억하는 함수다라고 말할 수 있을 것 같습니다.
참고자료
10분테코톡 - 엘라님
링크텍스트