[Javascript] Closure

Bam·2022년 3월 1일
0

Javascript

목록 보기
91/106
post-thumbnail

스코프 체인

클로저를 알기 전에 스코프 체인에 대해서 알아두어야합니다.

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

로컬 변수도 글로벌 객체처럼 어떤 객체에 의해서 관리가 되는데, 이 객체는 Call 객체(Activation 객체)라고 합니다. Call 객체도 인스턴스화나 메소드 호출이 불가능하고, 함수가 호출될 때 마다 로컬 변수들을 관리하기 위해서 자동적으로 생성되는 객체입니다.

그동안은 이 객체들은 자동적으로 생성되고 관리되어서 언급도 안하고 신경도 안썼지만, 이제는 좀 알아두어야합니다. 이 객체들을 알면 자바스크립트의 변수 메커니즘을 이해할 수 있기 때문입니다.

자 그럼 다시 돌아와서 스코프 체인은 Global 객체와 Call 객체들을 생성 순서대로이어놓은 것을 말합니다. 스코프 체인에서 변수의 참조는 체인의 선두(가장 내부의 변수)에서 시작해 가장 처음 만나는 변수 값을 그 값으로 채택하는 구조를 가집니다.

다음 코드를 보시면 스코프 체인이 어떤 느낌으로 이루어지는지 볼 수 있습니다.
변수 a, b, c는 가장 내부의 함수에서 호출되는데, 호출 위치에서 가장 선두 체인인 inner -> outer -> global순서로 참조할 변수를 찾아나감을 볼 수 있습니다.

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();

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());

이 코드의 실행결과가 어떨 것 같나요? 1을 인수로 준 것이 세번 출력하니까 '2, 2, 2'?

결과로는 '2, 3, 4'가 나왔습니다. 이러한 동작이 클로저입니다. 코드를 자세히 살펴보겠습니다.

closure() 함수는 init을 cnt에 저장하고, 그것을 1증가 시키고 반환하는 것 처럼 구성되어있습니다. 하지만 return 구문을 보시면, 그 값을 바로 반환하는 것이 아니라, 익명 함수를 통해서 값을 반환합니다. 그래서 함수가 종료되어도 익명 함수가 계속해서 cnt를 참조하고 있기 때문에 cnt의 값이 유지되는 것입니다. 그래서 첫 실행에 cnt가 2가 된 것이 계속 유지되어 3, 4와 같이 결과가 나타난 것 입니다.

아직 어렵나요? 좀 더 풀어서 설명하자면, c에는 closure()를 할당했습니다. closure()가 익명 함수를 반환하므로, 결과적으로는 c에 익명 함수가 할당된 상태인 것 입니다. 그런데 이 익명 함수는 스코프 체인으로 인해 closure의 로컬 변수 cnt를 참조하고 있으므로, c가 선언된 동안 cnt값이 계속해서 참조되어 초기화 되지 않고 남아있다는 것 입니다.

익명 함수로 보관되는 스코프 체인은 다음 세 값들을 보관합니다.

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

위 예제에서는 cnt가 클로저 함수의 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가 될까요?

이번에는 같은 클로저 함수를 사용했지만 서로 다른 값을 보관하고 유지합니다. 이 이유는 스코프 체인과 Call 객체 때문입니다. 처음에 Call 객체를 소개할 때 이런 이야기를 했습니다. Call 객체는 함수가 호출될 때 마다 생성된다. 따라서 Call 객체가 클로저 함수 호출마다 cnt를 관리하기 위해 별로도 생성된다는 것 입니다.

그래서 c1과 c2가 같은 클로저 함수를 호출해도 스코프 체인, Call 객체는 따로 생성되어 두 변수에 할당된 익명 함수가 참조하는 로컬 변수 cnt는 별개의 지역 변수가 된 것 입니다.

0개의 댓글