Programming/Javascript

[Javascript] Closure

Bam_t 2022. 1. 29. 18:19
728x90

1. 스코프 체인

Closure에 대해서 알아보기 전에, 스코프 체인에 대한 개념을 간단하게 짚고 넘어가겠습니다. 

자바스크립트는 스크립트가 실행 될 때, 내부적으로 Global 객체를 생성합니다. 이 객체는 인스턴스화하거나 메소드 호출이 불가능한 객체로, 스크립트에서 글로벌 변수/함수를 관리하기 위해 내부적으로 자동 생성되는 객체입니다.

마찬가지로 로컬 변수 또한 글로벌 객체와 유사한 객체로 관리됩니다. 이를 Call 객체(Activation 객체)라고 부르는데, Call 객체는 Global 객체처럼 인스턴스화나 메소드 호출이 불가능하며, 함수가 호출 될 때마다 로컬 변수들을 관리하기 위한 자동 생성 객체입니다.

이렇게 여태까지 몰랐어도 문제가 없었고, 직접 사용할 수도 없는 자동 생성 객체를 왜 굳이 다루냐면 이들을 알아야 자바스크립트의 변수 메커니즘들을 이해할 수 있기 때문입니다.

스코프 체인은 Global 객체와 Call 객체들을 생성된 순서대로 이어놓은 것을 의미합니다. 스코프 체인에서 변수의 참조는 체인의 선두(가장 내부의 변수)부터 점검하여 가장 처음 만나는 변수를 그 값으로 채택합니다.

다음 코드에서 inner에서 호출한 변수들이 어떠한 값들을 참조하고 있는지 알 수 있습니다. 변수 a, b, c는 선두의 스코프 체인인 inner() 함수부터 outer(), 글로벌(스크립트 전체)로 참조할 변수를 찾아서 나갑니다.

let c = 'Global';

const outer = () => {
    let b = 'Local, Outer';

    const inner = () => {
        let a = 'Local, inner';

        console.log(c);
        console.log(b);
        console.log(a);
    }
    inner();
}
outer();

 

 

2. Closure

Closure(이하 클로저)는 로컬 변수를 참조하는 함수 내부의 함수입니다. 간단한 클로저 구현 코드를 보면서 이야기하겠습니다.

const closure = init => {
    let cnt = init;

    return function () {
        return ++cnt;
    }
}

let c = closure(1);

console.log(c());
console.log(c());
console.log(c());

실행결과가 2 2 2 일 것이라고 예상하실 수도 있겠지만 실행결과는 2 3 4가 나왔습니다.

closure() 함수는 init을 cnt변수에 저장하고, 그것을 1 증가시켜 반환하는 것 처럼 동작할 것 같이 생겼습니다. 하지만 return에서 값을 바로 반환하는 것이 아니라 1을 증가시키는 익명 함수를 통해 값을 반환합니다. 그래서 이 익명 함수가 계속해서 로컬 변수 cnt를 참조하고 있기에, cnt값이 계속 유지되고 사용되는 것 입니다. 만약 1 증가시키고 반환했다면, 함수가 종료될 때 cnt의 값은 로컬 변수와 함께 사라져 값이 유지되지 않습니다.

좀 더 풀어 이야기 하자면, c에 closure 함수를 할당했습니다. closure 함수가 익명 함수를 반환하기 때문에, 결과적으로 c에는 익명 함수가 할당된 상태가 됩니다. 그런데 이 익명 함수는 스코프 체인으로 인해 closure의 지역 변수 cnt를 참조하고 있으므로 cnt의 값이 사라지지 않고, 유지가 되는 것 입니다.

익명 함수로 인해 보관되는 스코프 체인은 다음과 같습니다. 익명 함수가 유지되는 동안 아래의 세 값들은 보관됩니다.

  • 익명 함수를 가리키는 Call 객체
  • 클로저 함수의 Call 객체
  • 글로벌 객체

 

이런 항목과 동작들에서 알 수 있는 것은 클로저는 마치 기억 공간을 사용하는 것처럼 값을 보관/유지할 수 있다는 것 입니다. 그러면 클로저의와 스코프 체인을 이용한 또 다른 개념 하나를 보겠습니다. 아래 코드는 과연 어떻게 동작할까요?

const closure = init => {
    let cnt = init;

    return function () {
        return ++cnt;
    }
}

let c1 = closure(1);
let c2 = closure(900);

console.log(c1());
console.log(c2());
console.log(c1());
console.log(c2());

같은 지역 변수 cnt를 참조하고 있으니까 5가 될까요? 결과는 다음과 같습니다.

분명 같은 init을 참조할거라고 생각했지만, 별개의 값으로 처리되었습니다. 그 이유는 스코프 체인과 Call 객체에 있습니다. 맨 처음에 Call 객체에서 이런 말을 했습니다. Call 객체는 함수가 호출 될 때 마다 생성된다라고요.

c1과 c2는 같은 closure() 함수를 호출 했지만, 스코프 체인과 Call 객체는 개별적으로 생성되어서, 각 익명 함수에서 참조하고 있는 지역 변수 cnt는 별개의 지역 변수로 취급됩니다.


참조

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

 

클로저 - JavaScript | MDN

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

developer.mozilla.org

728x90