자바스크립트/자바스크립트 정리

(충격) 클로저는 사실 최약체다?

클로저는 어렵지 않아요

사실 클로저는 그렇게 어려운 개념은 아닙니다. 차근차근 클로저의 정의를 따라가다 보면 쉽게 이해가 가능합니다. 먼저 MDN 에서 한 번 그 정의를 찾아볼까요?

 

 

클로저는 중첩 함수가 상위 스코프 식별자를 참조하면서 외부 함수보다 중첩 함수가 더 오래 유지되는 경우에 이 중첩 함수를 클로저라고 부릅니다.

 

 

 

 

 

 

 

 

 

...????

 

....???

 

ㅠㅠㅠㅠ

 

 

왜 클로저는 어려운걸까??

클로저를 이해하기 위해서는 사실 선행 지식이 필요합니다. 이를 제쳐두고 바로 클로저는 무엇일까? 라는 궁금증에 클로저를 보게 된다면 앞에서 이해해야할 많은 지식들을 거꾸로 돌아가면서 이해하려 들게 됩니다. 

 

문제는 이 사전지식들이 어려운 편에 속한다는 점입니다. 사실 사전지식부터 열심히 공부하고 난 다음에 클로저를 이해하려고 한다면 그렇게 어렵지 않게 이해할 수 있습니다. 다시 한번 클로저의 정의에 대해서 살펴보겠습니다. 

 

클로저는 중첩 함수가 상위 스코프 식별자를 참조하면서 외부 함수보다 중첩 함수가 더 오래 유지되는 경우에 이 중첩 함수를 클로저라고 부릅니다.

 

이 클로저를 이해하기 위해서는 우리는 스코프를 알아야 합니다. 한 번 스코프에 대해서 간단하게 짚고 넘어가 보도록 하겠습니다.

 

사전 지식을 간단하게 알아보자

만약 변수가 선언되면 스코프라는 곳에(정확히는 렉시컬 환경의 환경 레코드입니다. 일단 패스!) 변수가 저장되게 됩니다. 코드를 통해서 살펴보겠습니다.

 

function foo() {
    const x = 1;
    const y = 2;
    
    function bar() {
        console.log(x); // foo 함수의 x 를 출력한다. foo 함수가 상위 스코프이므로.
    }
}

 

 

위 코드에서 foo 라는 함수에 x 와 y 가 선언-할당 되었습니다. 이때 foo 라는 함수의 스코프에 x 와 y 가 저장되게 됩니다. 그리고 그 밑에 bar 라는 함수가 foo 함수 안에 선언되어 있습니다. 이때 bar 함수 역시 스코프라는 공간을 가지고 있어 변수의 저장이 가능합니다.

 

자바스크립트의 함수는 선언된 공간을 상위 스코프로 가집니다. 상위 스코프란, 말 그대로 자신의 상위에 존재하고 있는 스코프로 자신에게 없는 값들을 찾을 수 있는 공간을 의미합니다. 상위 스코프에 값이 없다면 상위 스코프의 상위에게도 찾으러 가는것이 가능합니다.

 

돈이 없으면 아빠에게... 
아빠한테 돈이 없으면 할아버지에게....

 

bar 를 실행하게 되면 bar 안에 x 가 없지만 상위 스코프에 x 가 존재하므로 상위 스코프인 foo 함수의 x 인 1을 출력하게 됩니다. 만약 자기한테 값이 존재하지 않는다면 상위 스코프로 올라가면서 값이 존재하는지 찾게 된다는 것입니다.

 

즉, bar 함수는 foo 함수의 x 를 참조하고 있다고 얘기할 수 있습니다. 자바스크립트에서 만약 참조가 되지 않는 공간이 존재한다면 가비지 컬렉터에 의해서 제거되게 됩니다. 

 

그렇다면 아래 코드에서 foo 는 어떻게 될까요?

 

function foo() {
    const x = 1;
    const y = 2;
    
    function bar() {
        console.log(x); // foo 함수의 x 를 출력한다. foo 함수가 상위 스코프이므로.
    }
    
    return bar;
}

const barFunc = foo();
barFunc();

 

bar 에 foo 함수가 담겼습니다. 이때 foo 함수는 bar 함수를 return 하기 때문에 barFunc 에는 bar 함수가 담기게 됩니다. 즉, 더 이상 foo 함수는 참조되지 않습니다. 아무도 foo 함수를 가리키거나 foo 함수를 담고 있지 않기 때문입니다.

 

여기서 헷갈릴 수 있습니다. 지금 barFunc 에는 foo 함수가 담겼으니 foo 를 참조하는 것이 아니냐? 라는 물음이 생길 수 있기 때문입니다. 다시 말씀드리지만 foo 함수는 bar 함수를 return 합니다. 즉 foo 함수를 할당했지만 사실 barFunc 에 담긴것은 return 값인 bar 함수입니다. 

 

function foo() {
    return 1;
}

const barFunc = foo();
barFunc();

 

마치 위 코드에서 barFunc 가 1인 것과 같습니다. 여기서 정확히 이해를 하고 넘어가시길 바랍니다. 결국 더 이상 foo 함수를 참조하지 않기 때문에 가비지 컬렉터는 foo 함수를 없애버리고야 맙니다. 

 

하지만 여기서 bar 함수는 계속해서 살아있고 여전히 상위 스코프의 x 값을 참조하고 있습니다. 이게 바로 클로저입니다. 다시 한번 정의를 살펴보겠습니다.

 

클로저는 중첩 함수가 상위 스코프 식별자를 참조하면서 외부 함수보다 중첩 함수가 더 오래 유지되는 경우에 이 중첩 함수를 클로저라고 부릅니다.

 

1. 중첩 함수가 상위 스코프의 식별자를 참조하면서 .... -> bar 는 외부 함수인 foo 의 x 값을 참조하고 있습니다.

 

2. 외부 함수보다 중첩 함수가 더 오래 유지되는 경우에 ... -> foo 는 가비지 컬렉터에 의해 제거되었지만 bar 는 변수에 담겨 아직까지 살아있습니다.

 

즉, 여기서 barFunc 는 클로저라고 부를 수 있습니다.

 

function foo() {
    const x = 1;
    const y = 2;
    
    function bar() {
    
    // foo 함수의 x 값을 계속해서 참조하고 있다
        console.log(x); 
        
    }
    
    // foo 함수는 bar 를 return 한다.
    return bar;
}

// foo 함수가 담겼지만 사실 return 인 bar 가 담긴것이다.
const barFunc = foo();

// foo 함수를 더이상 참조하지 않아 가비지 컬렉터는 foo 를 삭제한다. 
// 이제 foo 보다 bar 함수가 더 오래 살아남았다.
barFunc();

클로저보다 강한 클로저의 사천왕들

사실 사전지식들이 그렇게 간단한건 아닙니다. 실행 컨텍스트에 대한 지식, 스코프체인에 대한 지식들은 이해하기에 굉장히 어려울 수 있습니다. 이번 글에서는 이 내용들의 간단한 개념만을 가지고 설명했습니다. 

 

클로저는 악명 높습니다. 하지만 그 악명에 비해서는 사실 빈 껍데기라고 볼 수도 있습니다. 클로저보다 클로저를 만나기 위해 넘어야 할 사천왕(??) 들이 너무나도 어렵기 때문입니다.

 

클로저를 지키는 사천왕 (실행 컨텍스트, 렉시컬 환경, 스코프 기타 등등...)

 

천천히 공부해보자!

사전지식들에 대해 정리한 글들을 이곳에 링크해놓을 예정입니다. 천천히 공부하면서 악명 높은 클로저를 넘어보도록 모두 힘내봐요!