클로저(Closure)

클로저라는 개념은 자바스크립트에 없는 class 의 역할을 대신해 비공개 속성/메서드, 공개 속성/메서드를 구현할 수 있는 근거를 마련할 수 있습니다.

따라서 객체지향적인 특징인 캡슐화(encapsulation)와 정보 은닉(information hiding)을 이해하려면 클로저를 반드시 이해해야 합니다.

클로저는 jQuery 같은 대형 라이브러리에서 채용되는 패턴입니다.




JavaScript Closure

함수를 정의하면 함수 단위의 렉시컬한 변수 스코프가 생성되고 변수 스코프의 체인이 구성됩니다.

즉, 중첩 함수의 경우 코드상에서 함수가 중첩된 그대로의 모습으로 변수 스코프 체인이 구성되어 변수에 대한 접근 권한도 코드의 계층구조 그대로 구성됩니다.


이제 다른 상황을 가정해 봅니다. 

자바스크립트의 함수 역할 중에 함수는 객체로서 다른 함수의 반환값으로 사용될 수 있습니다.

이 경우 반환값으로 사용된 함수의 변수 스코프의 문제를 생각해 보겠습니다.


다음의 코드에서 함수 inner 가 반환값으로 사용되어 다른 영역의 코드에서 실행되는 경우를 가정해 봅니다.

코드는 다음과 같습니다.

JAVASCRIPT
function outer() {
    var x = 0;
    return function () {
        return ++x;
    };
}

// 코드 실행
var x = -1;
var f = outer();
console.log(f()); // 1 반환


위 코드는 클로저가 생성되는 조건에 부합합니다.

  • 내부 함수가 익명함수로 되어 outer의 반환값으로 사용됐다.
  • inner는 outer의 실행 환경에서 실행된다.
  • inner에서 사용되는 변수 x는 outer의 변수 스코프에 있다.


이 프로그램이 실행되면 var f = outer(); 에 의해 파싱 단계에서는 outer의 내부에서 정의됐던 익명 함수가 실행 단계에서는 outer의 외부로 전달되어 실행된다.

앞에서 설명한 상황을 그림으로 나타내면 다음과 같습니다.



            클로저의 렉시컬 환경


실행 환경에 있는 f를 통해 outer가 반환한 익명 함수가 호출되면 return ++x; 에서 사용된 변수 x를 어디에서 검색할까?

원래 정의될 때 생성된 익명 함수의 변수 스코프 객체에서 갬색할까? 아니면 f가 실행되고 있는 영역에서 검색할까?

이는 렉시컬 특성과 유사합니다. 즉, 런터임의 변수는 렉시컬 환경을 기준으로 정의된 변수 스코프 및 체인에서 검색한다는 것입니다.


프로그램 실행 시 변수 검색은 해당 문장(return ++x)이 포함된 함수가 정의된 렉시컬 환경에서의 변수 스코프 체인을 기준으로 한다.


결국 앞의 예제 코드에서 inner를 호출하면 ++x 연산에 의해 1이 반환됩니다.


이제 좀 더 중요한 내용 다뤄보도록 합니다. 

함수 inner 를 계속해서 호출해서 결과를 보면 다음과 같습니다. 

JAVASCRIPT
f(); // 2 반환
f(); // 3 반환
f(); // 4 반환

결과만 봤을 때는 약간 이상하게 보일 수 있습니다.

어떻게 f의 호출이 끝나고 나서도 그 부모의 변수 스코프에 있는 x 값이 유지될 수 있을까?


내부 함수에서 선언된 변수가 아니면서 내부 함수에서 사용하는 outer의 x 같은 변수를 자유 변수(free variable)라고 합니다.

x가 메모리에서 제거되는 시기는 outer가 결정하지 못합니다. 이런 자유 변수는 outer가 실행되고 있는 환경이 "닫는(close)" 역할을 합니다.

즉, x의 경우는 변수 스코프가 outer가 실행되는 환경으로까지 확장됩니다.

외부환경에서 내부 함수에 대한 참조 f를 가지고 이상(즉 f가 메모리에서 사라지지 않는 이상) outer 함수는 "실행 중" 상태에 있습니다.

따라서 자유 변수 x 및 해당 변수 스코프 체인 관계는 메모리에서 계속 유지됩니다.

이처럼 outer 호출이 종료되더라도 outer의 지역 변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를 클로저(closure)라고 합니다.


함수 호출이 종료되더라도 그 함수의 지역 변수 및 지역 변수 체인 관계를 유지할 수 있는 구조를 클로저라고 한다.


자유 변수의 경우 그 값은 렉시컬 환경의 영향을 받으면서 그 생명주기는 실행 환경의 영향을 받는다는 것이 결국 클로저를 만들 수 있는 근거가 됩니다.

여기까지 이해하는 것만으로도 많은 것을 배운 것이지만 클로저에 대해 알아야 할 내용은 이뿐만이 아닙니다.


여기서 잠깐!!

다음의 Fucntion 으로 생성한 함수는 클로저를 만들지 못한다는 것을 알아야 합니다. Function 생성자를 이용해 생성한 함수는 렉시컬 영역을 사용하지 않습니다.

그 함수는 항상 전역 영역에서 생성된 것처럼 컴파일 될 것입니다.

JAVASCRIPT
var x = "g";
function f() {
    var x = "1";
    return new Function("return x"); // x를 전역 변수 스코프에서 검색한다.
}
var global = f();
console.log(global()); // g 를 출력한다.

위 코드는 Function 으로 생성된 함수로는 클로저를 구성할 수 없음을 보여주고 있습니다.


그럼 좀더 클로저에 대해 알아보도록 하겠습니다.




클로저 인스턴스

클로저가 생성한 인스턴스라는 의미로서 "클로저 인스턴스"라는 표현을 사용했습니다.

이것은 클로저를 인스턴스를 생성하는 단위로 보겠다는 것입니다.

클로저란 것은 내부 함수를 반환값으로 사용하는 특수한 함수로 볼 수 있습니다.

즉, 클로저를 함수 인스턴스를 만들어내는 특수한 함수로 해석할 수 있다는 것입니다.

일반 객체지향 프로그래밍 언어의 클래스 같은 존재와 비교해 클로저를 "함수를 생성하는 클래스"로 비유적으로 표현할 수 있을 것입니다.


클로저란 호출하면 다른 함수 인스턴스를 생성해내는 특수한 구조의 함수다.


앞에서 본 outer 함수를 클래스로 생각하고 다시 살펴봅니다.

JAVASCRIPT
// outer 정의
function outer() {
    var x = 0;  // 비공개 영역
    return function () { // 외부에서 호출 가능한 영역
        return ++x;  // 공개 영역
    }
}

outer 클로저를 이렇게 비공개 변수를 정의하는 부분과 외부에서 호출이 가능한 공개 영역으로 나눠서 생각해 보면 다른 언어의 클래스와 더욱더 유사해 보일 것입니다.

이제 "outer를 호출" 하는 것을 바로 "함수 객체를 생성"하는 것으로 생각하면 됩니다.

JAVASCRIPT
var f = outer(); // outer 의 인스턴스를 생성


클로저를 호출하는 것은 "클로저 인스턴스를 생성"하는 것이다.


클로저가 반환한 함수 f를 호출하는 것을 outer가 외부에 공개한 메서드를 호출하는 것으로 간주할 수 있습니다.

JAVASCRIPT
f(); // outer 의 공개 함수 사용


클로저 인스턴스를 호출한다는 것은 클로저가 외부에 공개한 멤버를 호출하는 것으로 이해할 수 있다.


"f" 가 사라지지 않는 이상 인스턴스 f가 가지고 있는 변수도 계속 유지됩니다.

이제 다음과 같은 코드를 살펴보겠습니다.

JAVASCRIPT
// outer 정의
function outer() {
    var x = 0;  // 비공개 영역
    return function () {
        return ++x;  // 공개 영역
    }
}

var f = outer();
f(); // 1
f(); // 2
var g = outer();
g(); // 1
g(); // 2

outer()를 호출해서 생성된 함수를 f에 할당했습니다. f를 호출해서 값을 증가시키면 다음에 f를 호출될 때는 이전에 증가된 값이 유지되어 두 번째 호출의 시작값이 됩니다.

함수 f 호출이 종료되더라도 내부 변수 x는 그대로 유지되는 클로저의 속성을 이용하고 있는 것입니다.


중요한 것은 지금부터입니다.

다시 한번 outer()를 호출해서 새로운 인스턴스를 g에 할당한 후 g를 호출해서 결과를 보면 "내부 변수 x가 새롭게 초기화"되었음을 알 수 있습니다.

내부 변수 x가 새롭게 초기화됐다는 것은 이전의 함수 f와 새롭게 생성된 함수 g는 전혀 다른 변수 공간을 사용하는 별도의 존재라는 것입니다.

클래스로 인스턴스를 생성할 때마다 자신만의 닫혀진 공간을 가진 인스턴스가 생성됩니다.

그래서 인스턴스에서는 다른 인스턴스의 닫혀진 공간에 있는 내부 변수에는 직접 접근할 수 없습니다.


클로저를 이렇게 닫혀진 공간을 가진 인스턴스를 생성하는 존재로 생각할 수 있습니다.

클로저를 호출하면 단순히 익명함수가 반환되는 것이 아니라 익명함수와 함께 거기에 연결된 닫혀진 공간이 함께 반환되는 것입니다.

그 닫혀진 공간에 내부 변수가 존재합니다.


클래스와 new를 사용해 여러 개의 닫혀진 공간을 가진 인스턴스를 만들어 내듯이 클로저와 ()를 이용하면 닫혀진 공간을 가지는 인스턴스를 여러 개 반복해서 만들어 낼 수 있습니다.

위의 f와 g는 이제 독립적인 변수 공간을 가지는 인스턴스입니다.

클로저 인스턴스는 단순히 클로저에서 반환된 함수에 대한 참조가 아니라 독립된 변수 공간을 가진 인스턴스를 반환하는 것입니다.

이것이 클로저의 진정한 의미입니다.


클로저란 비공개 내부 변수를 갖는 함수 인스턴스 생성자다. 

그리고 클로저로 생성한 독립된 변수 공간을 가진 인스턴스를 클로저 인스턴스라고 한다.