5-1 클로저의 의미 및 원리 이해
안녕하세요. 이번에는 챕터 5의 클로저에 대해 발표하도록 하겠습니다.
먼저 클로저가 무엇인지 알아보겠습니다. 클로저는 여러 함수형 프로그래밍 언어에서 등장하는 보편적인 특성입니다. 여러 책에서는 위에 보이는 사진과 같이 클로저에 대한 정의를 하고 있습니다.
여러분들은 어떤 정의가 가장 마음에 드시나요?
MDN 문서에 나와있는 클로저 정의에 대해 보겠습니다. 클로저는 함수와 그 함수가 선언될 당시의 lexical environment의 상호관계에 따른 현상을 말합니다.
'함수가 선언될 당시의 lexical environment'가 정확히 무엇을 의미할까요? 저번에 배웠던 내용 중 하나인데 기억나시나요?
바로 outerEnvironmentReference입니다. 만약 위 사진과 같이 콜스택이 있다고 했을 때 innerFunc의 outerEnvironmentReference는 outer의 lexical environment가 됩니다.
상호관계는 정확히 어떤걸 말할까요??
아까 봤던 콜스택을 다시 한번 살펴봅시다. innerFunc가 outer의 lexical environment를 항상 사용하는 것은 아닙니다. innerFunc의 environmentRecord에서에 이미 있는 변수라면 outer를 참조하지 않을겁니다.
즉, 여기에서 말하는 상호관계란 내부함수(innerFunc)에서 외부 변수를 참조하는 경우에 한해서만 발생합니다.
한번 예시를 보겠습니다. 먼저 외부 함수에서 변수를 선언하고 내부함수에서 해당 변수를 참조하는 형태입니다.
코드를 살펴보면 inner 함수 내부에서는 a를 선언하지 않았기 때문에 environmentRecord에서 값을 찾지 못합니다. 따라서 outerEnvironmentReference에 지정된 상위 컨텍스트인 outer의 lexical environment에 접근해 a를 찾게 됩니다.
outer함수의 실행 컨텍스트가 종료되면 lexical environment에 지정된 식별자들 (a, inner)에 대한 참조를 지웁니다. 각 주소에 저장돼 있던 값들은 자신을 참조하는 변수가 하나도 없게 되죠. 즉, 참조 카운트가 0이 되어 가비지 컬렉터의 수집 대상이 됩니다.
이번에는 비슷하지만 조금 다른 코드를 보겠습니다. outer 함수는 inner 함수를 return합니다. outer 함수의 실행 컨텍스트가 종료될 때 outer2 변수는 outer의 실행 결과인 inner 함수를 참조합니다.
outer2()를 실행하면 inner 함수가 실행되어 a가 2가 됩니다. 그 다음 console.log에는 3이 찍히겠네요.
inner함수의 실행 시점에는 outer 함수는 이미 실행 종료 상태인데 outer 함수의 lexical environment에 어떻게 접근할 수 있을까요?
여기서 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있으면 그 값은 수집 대상에 포함시키지 않습니다.
아까 봤던 코드를 다시 보면, outer의 실행이 종료되어도 outer2를 실행함으로써 내부 함수 inner가 호출될 수 있었죠. inner 함수의 실행컨텍스트가 활성화되면 outerEnvironmentReference가 outer함수의 lexical environment를 필요로 하게 됩니다. 따라서 outer함수의 a는 가비지 컬렉터가 수집하지 않습니다.
즉, 클로저란 어떤 함수 A에서 선언한 변수 a를 참조하는 내부함수 B를 외부로 전달한 경우 A의 실행 컨텍스트가 종료된 이후에도 변수 a가 사라지지 않는 현상을 말합니다.
앞서 말했던 외부 전달이 꼭 return을 통한 전달만을 의미하는 것은 아닙니다. 위 코드들도 지역변수를 참조하는 내부함수를 외부에 전달한 예시인데요.
왼쪽 코드 같은 경우 window 메서드는 setInterval도 별도의 외부객체입니다. 즉 inner라는 내부함수를 setInterval로 전달했기 때문에 클로저가 발생하죠.
오른쪽 코드 같은 경우도 비슷합니다. 콜백함수를 click 이벤트 리스너에 전달함으로써 count 변수에 클로저가 발생했죠.