Skip to content

클로저는 어떻게 가능한가 ​

지난 글 끝에서 답만 확인하고 넘어간 코드가 있다.

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

const innerFunc = outer(); // outer는 이미 종료됐다
innerFunc(); // 10

답이 10이라는 건 알겠는데, 사실 나는 이게 더 찜찜했다. 지역 변수는 함수가 끝나면 사라진다고 배웠다. outer()는 inner를 반환하고 이미 종료됐고, 실행 컨텍스트도 스택에서 제거됐을 것이다. 그런데 x는 왜 아직 살아있을까?

"함수가 끝나면 지역 변수도 사라진다"는 절반만 맞다 ​

찾아보니 내가 하나로 뭉뚱그려 생각하던 걸 엔진은 둘로 나눠 관리하고 있었다. 실행 컨텍스트와 렉시컬 환경(변수들이 실제로 저장되는 곳)은 별개다.

outer()가 종료되면 실행 컨텍스트는 스택에서 제거된다. 여기까지는 배운 대로다. 하지만 그 컨텍스트가 가리키던 렉시컬 환경 — x: 10이 저장된 곳 — 은 스택이 아니라 힙에 있는 객체고, 누군가 참조하는 한 가비지 컬렉터(GC)가 회수하지 않는다.

그럼 누가 참조하고 있나? 지난 글에서 함수는 자신이 정의된 환경을 [[Environment]]에 저장한다고 했다. inner는 outer의 렉시컬 환경을 [[Environment]]로 참조하고, 전역의 innerFunc가 inner를 참조한다. 참조 사슬이 끊기지 않았으니 환경은 살아남는다.

outer의 실행 컨텍스트는 스택에서 제거됐지만, inner 함수 객체가 [[Environment]]로 outer의 렉시컬 환경을 참조하므로 힙에 살아남는 구조

이 현상에 이름이 붙어 있다. 클로저(closure) — MDN은 "함수와 그 함수가 선언된 렉시컬 환경의 조합"이라고 정의한다. 지난 글에서 "함수는 자신이 태어난 스코프를 기억한다"고 했는데, 클로저는 그 기억이 원래 주인(외부 함수)보다 오래 살아남은 상태다.

이름의 유래도 알고 나니 와닿았다. 클로저가 참조하는 상위 스코프의 변수(x 같은 것)를 자유 변수(free variable)라고 부르는데, 클로저는 "자유 변수에 닫혀 있는(closed over) 함수"라는 뜻이다.

그럼 모든 함수가 클로저인가? ​

이론적으로는 그렇다. 모든 함수는 정의될 때 [[Environment]]에 상위 환경을 저장하니까. 하지만 전부 클로저라고 부르면 용어가 의미를 잃는다. 일반적으로는 두 조건을 만족할 때만 클로저라고 부른다.

첫째, 상위 스코프의 식별자를 실제로 참조해야 한다.

javascript
function foo() {
  const x = 1;

  function bar() {
    const z = 3;
    console.log(z); // 상위 스코프의 식별자를 참조하지 않는다
  }
  return bar;
}

bar는 x를 쓰지 않는다. 사실 이 경우 모던 엔진은 최적화를 해버린다 — 참조하지 않는 변수는 애초에 기억할 환경에서 제거한다. 브라우저 디버거로 스코프를 열어봐도 x가 보이지 않는다. 기억하는 게 없으니 클로저라고 부르지 않는다.

둘째, 외부 함수보다 오래 살아남아야 한다.

javascript
function foo() {
  const x = 1;

  function bar() {
    console.log(x); // 참조는 하지만
  }
  bar(); // 반환되지 않고 여기서 호출되고 끝
}

bar는 x를 참조하지만 foo가 끝나기 전에 소멸한다. 외부보다 오래 살지 않으니 "기억이 주인보다 오래 남는" 클로저의 핵심 현상이 없다. 처음의 outer/inner처럼 참조하면서 + 더 오래 살아남는 경우가 우리가 클로저라고 부르는 것이다.


클로저는 렉시컬 스코프와 [[Environment]]가 있는 언어에서 자연스럽게 따라 나오는 결과다. 함수가 스코프를 기억하는데, 그 함수가 밖으로 살아나가면? 기억도 같이 살아나간다. 이게 전부다.

여기서 우리는 Why를 다시 생각해볼 수 있는데, 그렇다면 이걸 대체 어디에 쓰는 걸까? 생각보다 우린 자주 사용하고 있었다. → 클로저는 어디에 쓰는가