클로져(closure)

minho·2021년 12월 6일
0

closure의 의미는 뭘까?


closure란 내부함수와 LexicalEnvironment의 조합이라고 할 수 있다.

이 상황은 무엇을 말하는 것일까?

그림은 어떤 실행컨텍스트A에서 함수 B를 선언한 것이다.
closure란 내부함수와 LexicalEnvironment의 조합이라고 했으므로 A의 LexicalEnvironment와 내부함수 B의 조합이라고 할 수 있다.
mdn문서를 참조하면 클로저란 함수가 생성될 때 매번 같이 발생한다.
그러므로 클로저란 특별한 개념이 아니라 함수의 생성과 함께 생기는 '당연한' 개념인 것이다.
그러나 클로저란 상황을 이런 보편적인 상황에 모두 적용하지는 않는다.
클로저 환경에서만 발생하는 무언가 특별한 현상을 표현하기 위해서 이 '클로저'라는 단어를 사용한다.
그러므로 '특별한 현상'에 주목해야 할 필요가 있다.
다시 정리를 하자면 클로저는 A의 LexicalEnvironment와 내부함수 B의 조합에서 나타나는 특별한 현상이라고 할 수 있다.
A와 B의 조합이라고 하면 B의 outerEnvironmentReference에서 A의 environmentRecord를 참조할 수 있으므로 조합의 경우는 이경우 밖에 없다.
그러므로 이를 다시 정리하자면 위와같이 정의할 수 있다.

그렇다면 '특별한 현상'이란 무엇인가?


위의 코드를 함께 실행해보며 어떻게 스택이 쌓이고 없어지는지 살펴보자

먼저 전역콘텍스트가 실행되면 environmentRecord가 수집을 시작한다.
여기서는 outer함수와 outer2함수를 수집한다.
outer2함수는 outer함수가 끝나야 값이 지정되므로 아직은 갑이 undefined로 되어 있다.


이 상태에서 outer를 호출하면 outer에 대한 실행컨텍스트가 쌓이면서 a=1이되고 inner에는 함수가 들어간다.
실행기 끝나면 inner가 반환되므로 반환된값이 outer2로 들어가게 된다.
종료된 outer는 outer2에 의해서 언젠가 inner함수가 다시 호출될 수 있으니 완전히 종료되지 않는다.

좀 더 자세히 설명하자면 inner 내부에서 참조하고 있는 outerEnvironmentReference상의 변수 a는 outer2에 의해서 언제든지 사용할 수도 있기 때문에 outer의 실행컨텍스트는 종료되었지만, 변수a는 살아있다고 할 수 있다.(a는 참조카운트가 0이 되지 않았다.)

outer2가 실행되어 inner가 실행되고 inner는 outerEnvironmentReference로 outer의 변수a를 참조하므로 outer의 a는 2로 된다.

한번더 outer2가 출력되면서 outer의 a는 3이 된다.

출력후 inner도 종료되고 전역컨텍스트가 종료된다.

a는 전역컨텍스트가 종료되기 전까지 끈질기게 살아남는다.
outer2 변수가 inner함수를 물고 있는데, 다시 inner함수의 outerEnvironmentReference로부터 a를 참조하기 때문이다.
즉, a의 참조카운트가 0이 되지 않아서이다.

그렇다면 a의 참조카운트를 0으로 만드려면 어떻게 해야 할까?

간단하게 outer2변수에 다른 걸 대입하면 된다.

여기서 알 수 있듯이 변수a가 사라지지 않고 계속해서 살아있는 특별한 현상이 클로저의 '특별한 현상'이다.

클로저를 어떻게 활용할까?


클로저를 사용하면 지역변수가 함수 종료 후에도 사라지지 않게 할 수 있다.

예시를 들여다 보자

roy.name을 출력하라 하면 roy객체에는 name에 대한 프로퍼티가 없지만 name에 대한 getter가 있기 때문에, get name()이 실행되고 _name변수는 user함수를 호출할 때 매개변수로 넘겨받은 값을 가지는 user함수에서 선언된 변수이다.

원래대로라면 _name는 user함수의 실행컨텍스트가 종료됨과 동시에 함께 사라져야 하지만 return안에서 _name을 사용하는 함수가 있으므로(참조카운트가 0이 아니므로) 나중에 쓸 변수이구나 판단해서 살려두는 것이다.

그래서 roy.name의 값이 '재남'일 수 있는 것이다.

roy.name의 값을 '제이'로 바꾸려고 하면 set name이 발동돼서 _name의 값이 '제이'로 바뀌게 된다.


그렇다면 roy._name을 실행하면 어떻게 될까?
roy객체에 _name이라는 프로퍼티는 없으므로 기존의 값인 '제이'를 출력하게 된다.
한편 getter인 status를 호출하면, _logged가 return된다.
변수 _logged의 값은 true이므로 true값인 'login'이 출력된다.

이상태에서 logout이라는 메소드를 호출하면, _logged가 false가 되고, roy.status를 출력하면 'logout'을 반환한다.
한편 roy의 status는 getter는 있지만 setter는 없다.
그러므로 status의 상태를 변경할 수 없다.
그 결과 roy.status=true;의 값은 무시가 된다.
따라서 roy.status를 출력하면 여전히 같은 값인 'logout'이 출력된다.

이 예제에서 알 수 있는 것은 _name과 _logged는 함수 종료 후에도 사라지지 않고 값을 유지하는 것을 알 수 있다.

나아가 이 변수들을 외부로 부터 보호할 수 있다.
위의 roy.status=true와 같이 setter가 없으면 값이 변경되지 않는 것 처럼 말이다.
직접적인 변화를 줄 수 있는건 login(), logout()만을 통해 변경할 수 있으며 이런것을 '캡슐화'라고 한다.

profile
Live the way you think

0개의 댓글