[JS] 클로저(Closure) 에 대한 고찰 (참조 원리에 의한 이해)

UI SEOK YU·2023년 5월 3일
0

JavaScript

목록 보기
3/6
post-thumbnail

이해가 안 되는 설명

??? : "클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. "

  • 도대체 이게 무슨 말인지 이해가 가는가?

  • 어휘적 환경을 렉시컬 환경이라고 해석해도 환경의 조합이라는 말이 이해가 잘 가지 않는다.

  • 많은 강의와 도서에서 클로저를 단순히 상위 함수의 변수를 기억하는 것으로 설명한다.
    혹은 이해를 못할까봐 설명에 도약을 두어 더 혼란스럽게 만들기도 한다.

  • 하지만 클로저의 원리를 완전히 파헤쳐 보면, 왜 그렇게 동작하는지 추가적인 이론과 규칙 없이도 설명이 가능하다.




클로저가 발생하는 원리

  1. 함수는 실행되면 콜 스택에 올라가며, 콜 스택에 올라간 함수 컨텍스트는 자신이 가지고 있는 정보에 대해 초기화를 한다.

  2. 컨텍스트는 Variable Environment, Lexical Environment 등을 생성한다.

  3. 자신이 가진 변수와 함수(메서드)객체를 생성하여 그것들의 주소를 Lexical Environment 안의 Environment Record에 기록한다.

  4. 함수(메서드)객체 내부에는 [[Environment]] 슬롯을 생성하여, 자신의 Lexical Environment 주소를 기록해준다. (선언된 스코프의 주소를 기록)

  5. Variable Environment, Lexical Environment 에 있는 OuterEEnvironment Reference 에는 자신이 "선언"된 부모의 Lexical Environment 의 주소를 적는다. 이 주소는 본인의 [[Environment]] 슬롯에 기입된 (선언적)부모 함수의 Lexical Environment 주소를 복사하여 사용한다.

4번은 자신의 Lexical Environment를 메서드의 [[Environment]] 슬롯에 기록하는 것이고,
5번은 본인의 [[Environment]]을 확인해서 자신의 부모 주소를 알아오는 것이다.
헷갈리지 말자.

  1. 자신이 참조해야하는 변수나 메서드가 본인의 Environment Record 에 없다면, OuterEnvironment Reference 에 기록된 자신의 부모 함수의 Lexical Environment를 참조하여 사용한다.

예시

  1. 어떤 함수 Outer가 실행된다. 내부에는 inner함수가 정의되어 있다.

  2. 콜 스택에 올라간 Outer는 초기화를 한다. Variable Environment, Lexical Environment를 생성한다.

  3. 자신이 가진 변수와 메서드를 가리킬 메모리를 생성하고, 그 메모리들의 주소를Lexical EnvironmentEnvironment Record에 기록한다.

  4. Lexical EnvironmentOuterEnvironment Reference 주소를 기록하려고 한다. Outer함수 객체 자신이 생성될 때 기록된 [[Environment]] 를 보니, 자신의 부모스코프는 전역스코프이다. 이 주소를 기록한다.

  5. Outer의 컨텍스트 설정을 마치고 이어서 실행되니, inner 함수를 반환하고 종료되는 로직이다.

  6. Outer는 컨텍스트는 종료되었고, Inner 함수가 반환되었다. 이걸 어떤 변수에 잠깐 저장하자.

  7. 받아 둔 Inner 함수를 실행시키자 콜 스택에 컨텍스트로 올라갔다.

  8. Inner 컨텍스트도 자신의 렉시컬 환경을 설정한다. 자신의 변수와 메서드가 들어갈 메모리를 할당하고, 자신의 Environment Record에 기록해 둔다.

  9. OuterEnvironment Reference를 기록하려고 하니, 앗? 내가 누구로부터 선언되었는지 모르겠다. 자신이 실행되기전에 자신의 부모 컨텍스트 Outer는 없어져 버렸다!

  10. 다행히 자신의 족보인 [[Environment]] 를 보니, 부모 컨텍스트의 환경이 기록된 주소가 남아 있다. 이걸 가져다가 자신의 OuterEnvironment Reference 에 적는다.

  11. 부모 컨텍스트가 가졌던 환경이 기록된 주소, 즉 Outer의 Lexical Environment가 소실되지 않고 남은 이유는, 원리 4번과 같이 미리 부모의 렉시컬 환경을 적어두어 참조하는 객체(Inner의 [[Environment]])가 있었기에, GC로부터 제거되지 않았던 것이다.
    (이렇게 클로저가 발생힌다)

  12. Inner는 실행되면서 자신에게 없는 변수들을 OuterEnvironment Reference 타고 조상의 Lexical Environment를 참조할 수 있었다.

  13. 실행을 마치고 Inner 컨텍스트도 종료되었다.

  14. Inner의 [[Environment]]OuterEnvironment Reference 로 부터 참조를 받던 Outer의 Lexical Environment는, Inner 컨텍스트가 종료됨에 따라 자신을 찾는 객체가 전부 없어져 버렸다. 참조카운트가 0이므로, GC에 의해 메모리는 회수된다.
    (이렇게 클로저는 종료된다)


결론

즉, 클로저는 OuterEnviroment와 [[Enviroment]] 가 Environment Record 의 주소를 참조하므로, Environment Record 의 참조카운트가 0 이 아니기 때문에 발생한다.

정말 기본적이고 근본적인 원리에 의해 클로저 현상이 생기는 것이다.


"클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다" 라는 말은,
함수 본인의 렉시컬 환경 + 함수의 [[Environment]]OuterEnvironment Reference의 참조로 인해
GC로 부터 '제거되지 않은 상위함수(=함수가 선언된 곳)의 Lexical Environment'를 모두 사용이 가능하다는 것을 의미한다.



참고서적

0개의 댓글