[TIL] 클로저

김은혁·2022년 2월 10일
0

📚 모던 자바스크립트 deep dive 교재를 참고하여 작성합니다

클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합입니다.

상당히 난해한 정의입니다. 여기서 핵심적인 표현은 함수가 선언된 렉시컬 환경입니다.

const x = 1;

function outer() {
    const x = 10;
  
    function inner() {
        console.log(x); // 10
    }
    inner();
}
outer();
const x = 1;

function outer() {
    const x = 10;
    inner();
}
function inner() {
    console.log(x); // 1
}
outer();

위의 코드 예시처럼 outer 함수 내부에서 inner 함수가 정의되고 호출했을 때와 inner 함수가 outer 함수 외부에서 선언되고 내부에서 호출되었을 때의 결과는 다르게 나타납니다. 이러한 현상이 발생하는 이유는 자바스크립트가 렉시컬 스코프를 따르는 프로그래밍 언어이기 때문입니다.

그렇다면 렉시컬 스코프가 무엇일까요 ?

자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정합니다. 이를 렉시컬 스코프(정적 스코프)라고 합니다.

const x = 1;

function foo() {
    const x = 10;
    bar();
}
function bar() {
    console.log(x);
}
foo(); // 1
bar(); // 1

foo 함수와 bar 함수는 모두 전역에서 정의된 전역 함수입니다. 함수의 상위 스코프는 어디서 정의했느냐에 따라 결정되므로 foo 함수와 bar 함수의 상위 스코프는 모두 전역입니다. 함수의 호출 위치는 상위 스코프 결정에 어떠한 영향도 주지 않습니다.

스코프의 실체는 실행 컨텍스트의 렉시컬 환경입니다. 이 렉시컬 환경은 자신의 외부 렉시컬 환경에 대한 참조를 통해 상위 렉시컬 환경과 연결됩니다. 따라서 함수의 스코프를 결정한다는 것은 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정한다는 것입니다.

즉 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정됩니다. 이것이 바로 렉시컬 스코프입니다.

함수가 정의된 환경과 호출되는 환경은 다를 수 있습니다. 따라서 렉시컬 스코프가 가능하려면 함수는 자신이 호출되는 환경과는 상관없이 자신이 정의된 환경, 즉 상위 스코프를 기억해야 합니다. 이를 위해 함수는 자신의 내부 슬롯에 상위 스코프의 참조를 저장합니다.

const x = 1;

// 1
function outer() {
    const x = 10;
    const inner = function () {
        console.log(x);
    }; // 2
    return inner;
}

const innerFunc = outer(); // 3
innerFunc(); // 4 

outer 함수를 호출(3)하면 outer 함수는 중첩 함수 inner를 반환하고 생명 주기를 마감합니다. 즉, outer 함수의 실행이 종료되면 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 제거됩니다. 이때 outer 함수의 지역 변수 x와 변수 값 10을 저장하고 있던 outer 함수의 실행 컨텍스트가 제거되었으므로 outer 함수의 지역 변수 x 또한 생명 주기를 마감합니다.

하지만 위 코드의 실행 결과(4)는 outer 함수의 지역 변수 x의 값인 10입니다. 이처럼 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있습니다. 이러한 중첩 함수를 클로저라고 합니다.

글의 처음에 언급한 함수가 선언된 렉시컬 환경이란 함수가 정의된 위치의 스코프, 즉 상위 스코프를 의미하는 실행 컨텍스트의 렉시컬 환경을 말합니다. outer 함수의 실행이 종료되면 inner 함수를 반환하면서 outer 함수의 생명 주기가 종료되어 outer 함수의 실행 컨텍스트가 실행 컨텍스트 스택에서 제거되지만, outer 함수의 렉시컬 환경까지 소멸하는 것은 아닙니다.

0개의 댓글