[JS] 클로저

cabbage·2023년 6월 13일

JS

목록 보기
35/43
post-thumbnail

기술면접을 준비하는 과정에서 자바스크립트의 클로저 개념에 대해 공부하였고 클로저 개념에 대해 정리한다. 이 정리글은 코어 자바스크립트(정재남)를 참고하여 작성하였다.

클로저

처음에 클로저 개념을 공부하면서 무슨 말인지 감을 잡을 수 없었는데, 아래의 클로저 설명을 보고 클로저에 대한 느낌을 얻을 수 있었다. 클로저의 정확한 정의는 아니고, 클로저를 좀 더 이해하기 쉽게 하기 위해 고쳐서 설명한 정의이다.

클로저는 어떤 함수에서 선언한 변수를 참조하는 내부함수를 외부로 전달하는 경우 함수의 실행 컨텍스트가 종료된 이후에도 해당 변수가 사라지지 않는 현상을 말한다.

클로저는 '어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상'이다. 아래 예시들을 통해 클로저가 무엇인지 감을 잡을 수 있었다. 외부함수에서 변수를 선언하고, 내부함수에서 해당 변수를 참조하는 형태의 예시들이다.

예시 1.(클로저가 발생하지 않음)

var outer = function () {
	var a = 1;
  	var inner = function () {
    	console.log(++a);
    };
  	inner();
};

outer(); // 2

outer 함수에서 변수 a를 선언하고, outer 함수의 내부함수인 inner 함수에서 변수 a의 값을 1만큼 증가시킨 후 출력하고 있다.

  • inner 함수 내부에서는 변수 a를 선언하지 않았으므로 스코프 체인을 통해 outer 함수 실행 컨텍스트의 LexicalEnvironment에 접근하여 변수 a를 참조한다.

outer 함수가 종료되면(outer 함수 실행 컨텍스트가 콜스택에서 사라짐) LexicalEnvironment에 저장된 식별자에 대한 참조가 삭제된다.

  • 이 경우 각 주소에 저장되어 있던 값들은 자신을 참조하는 변수가 하나도 없게 되어 가비지 컬렉터의 수집 대상이 된다.

결국 outer 함수 실행 컨텍스트가 사라지면 LexicalEnvironment에 저장된 식별자 정보들도 사라지기 때문에 변수 a에 저장된 값들도 가비지 컬렉팅되어 사라진다. 클로저의 정의에 부합하지 않는다.

예시 2.(클로저가 발생하지 않음)

var outer = function () {
    var a = 1;
    var inner = function () {
        return ++a;
    };
    return inner();
};

var outer2 = outer();
console.log(outer2); // 2

inner 함수 내부에서 외부변수 a를 참조한다. outer 함수는 inner 함수 실행 결과를 리턴하므로 결과적으로 outer 함수가 종료되면(outer 함수 실행 컨텍스트가 콜스택에서 사라짐) 변수 a를 참조하는 대상이 사라진다.

예시 1과 마찬가지로 a, inner 변수의 값들은 가비지 컬렉팅되어 사라진다. 클로저의 정의에 부합하지 않는다.

예시 3.

위 예제들과 달리 outer 함수 종료 후에도 inner 함수를 호출할 수 있게 외부로 전달한다면 어떻게 될까.

var outer = function () {
    var a = 1;
    var inner = function () {
        return ++a;
    };
    return inner;
};

var outer2 = outer();
console.log(outer2()); // 2
console.log(outer2()); // 3

outer 함수는 inner 함수 자체를 반환하고 있다.

  • outer 함수의 실행 컨텍스트가 종료될 때 outer2 변수는 outer 함수의 실행 결과인 inner 함수를 참조하게 된다.
  • outer2를 호출하면 inner 함수가 실행된다.

inner 함수에는 어떤 변수도 존재하지 않으므로 inner 함수 실행 컨텍스트의 environmentRecord에 담길 식별자 정보가 없다. 그리고 inner 함수 실행 컨텍스트의 outerEnvironmentReference에는 inner 함수가 선언된 위치의 LexicalEnvironment가 참조복사된다.

  • inner 함수는 outer 함수 내부에서 선언되었으므로 inner 함수 실행 컨텍스트의 outerEnvironmentReference에는 outer 함수의 LexicalEnvironment가 담긴다.
  • outer2를 호출하면 inner 함수가 실행되므로 스코프 체인을 통해 outer 함수에서 선언한 변수 a에 접근해 1을 증가시키고 그 값인 2를 반환한 후 inner 함수의 실행 컨텍스트가 종료된다.
  • 다시 outer2를 호출하면 같은 방식으로 a의 값을 1 증가시키고 그 값인 3을 반환한다.

예시 3의 코드가 이상한 점

예시 3은 클로저가 발생한다. 그 이유에 대해 알아보자.

inner 함수 실행 시점에 outer 함수는 이미 실행이 종료된 상태이다. 그런데 inner 함수 실행 컨텍스트의 outerEnvironmentReference는 outer 함수의 LexicalEnvironment에 접근하고 있다. 이것은 가비지 컬렉터의 동작 방식 때문에 가능하다.

  • 가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있다면 그 값은 수집 대상에 포함시키지 않는다.
  • outer 함수는 inner 함수를 반환한다.
  • outer 함수가 종료되어도 내부함수 inner 함수는 언젠가 outer2를 실행함으로써 호출될 가능성이 존재한다.
  • 언젠가 outer2를 실행함으로써 inner 함수 실행 컨텍스트가 활성화되면 outerEnvironmentReference가 outer 함수의 LexicalEnvironment를 필요로 하게 된다. outer 함수의 LexicalEnvironment는 가비지 컬렉터의 수집 대상에서 제외된다.
  • 따라서 inner 함수가 outer 함수의 a 변수에 접근할 수 있게 된다.

함수의 실행 컨텍스트가 종료된 이후에도 LexicalEnvironment가 가비지 컬렉터의 수집 대상에서 제외되는 경우는 지역변수를 참조하는 내부함수가 외부로 전달된 경우뿐이다.

  • outer 함수의 실행 컨텍스트가 종료된 이후에도 outer 함수의 LexicalEnvironment가 메모리에 존재하는 이유는 outer 함수의 지역변수 a를 참조하는 내부함수 inner가 외부로 전달되었기 때문이다.

클로저 설명에서 언급한 '어떤 함수에서 선언한 변수를 참조하는 내부함수에서만 발생하는 현상'은 '외부 함수의 LexicalEnvironment가 가비지 컬렉팅되지 않는 현상'을 말하는 것이다.

정리

다음과 같은 절차에 따라 클로저가 발생한다고 정리하였다.

  1. 함수 A에서 변수 a를 선언한다.
  2. 함수 A의 내부함수 B가 함수 A의 변수 a를 참조한다.
  3. 함수 A를 호출한다.
  4. 함수 A가 내부함수 B를 리턴하여 내부함수 B를 외부로 전달하고 함수 A의 실행을 종료한다.
  5. 함수 A의 실행 컨텍스트가 종료되지만(함수 A가 종료되지만) 함수 A의 LexicalEnvironment는 가비지 컬렉팅되지 않는다. (클로저가 발생하였다.)
  6. 함수 A의 변수 a를 참조하는 내부함수 B가 외부로 전달되었기 때문에 내부함수 B는 언젠가 호출될 가능성이 존재하기 때문이다. 외부로 전달된 내부함수 B는 변수 a를 참조하므로 변수 a 정보를 담고 있는 함수 A의 LexicalEnvironment는 메모리에 존재해야 한다.

참고

  • 코어 자바스크립트 - 정재남
profile
캐비지 개발 블로그입니다. :)

0개의 댓글