본문 바로가기

개발

JavaScript 클로저

자바스크립트에서 Closure가 무엇인지 정리를 해보려고 한다. Closure가 워낙 다양한 방식으로 설명되다보니 필요 이상으로 어렵게 느껴지는 면이 있는 것 같아서 내가 이해한대로 설명을 적어보는게 도움이 될 것 같다.

정의

MDN의 설명에 따르면 클로저는 어떤 함수와 그 함수가 레퍼런스를 가진 lexical 환경의 합을 의미한다.

 

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).

어떤 코드가 실행될 때 상위 스코프의 변수들이 사용될 수 있고 이와 관련된 개념으로 Lexical Scope, Scope Chain 등이 있다.

 

사실 위 정의나 설명만 봐서는 클로저와 스코프의 차이를 알기 어렵다. 클로저의 가장 중요한 포인트(?)는 상위 스코프에 대한 레퍼런스가 그 상위 스코프 환경의 실행이 종료된 뒤에도 남아있다는 점이다.

 

function outer() {
  const a = 1;

  return function inner() {
    console.log(a);
  }
}

const foo = outer();

foo(); // 1

 

outer 함수는 inner 함수를 반환한다. inner 함수는 자신의 상위 스코프의 a 값을 출력한다. fooouter에서 반환한 함수를 가리킨다. foo를 호출하면 1이 출력된다. (예시에서는 설명을 위해 내부 함수에 inner라는 이름을 붙였지만 익명 함수여도 똑같이 동작한다.)

 

foo, 즉 inner 함수가 호출되는 시점에는 이미 outer 함수가 종료된 상태임에도 a 값이 문제 없이 출력된다. 여기서 inner 함수와 a를 포괄적으로 Closure라고 한다. 콘솔창에서도 Closure를 다음과 같이 확인해볼 수 있다.

 

// 위 코드 실행 후
console.dir(foo);

// ƒ inner()
// ...
// [[Scopes]]: Scopes[3]
// 0: Closure (outer) {a: 1}
// 1: Script {foo: ƒ}
// 2: Global {0: Window, window: Window, …}

특징

함수를 여러번 호출해도 유지된다

function outer() {
  let a = 1;

  return function inner() {
    console.log(a++);
  }
}

const foo = outer();

foo(); // 1
foo(); // 2
foo(); // 3

 

inner 함수가 a를 출력하고 값을 1 증가시키도록 수정했다. foo를 여러 차례 호출하면 증가된 a가 출력되는 것을 확인할 수 있다.

새로운 함수를 리턴하면?

// 위 코드에서 이어짐

const bar = outer();

bar(); // 1

foo(); // 4

outer 함수가 새로 a값을 선언하고 새로 inner 함수를 반환하기 때문에 새로운 클로저가 만들어졌다.

Privacy

위 예시를 보면 변수 a에 접근할 수 있는 방법은 반환된 함수를 이용하는 것 뿐이다. 즉 a값을 임의로 혹은 실수로 변경하는 일을 원천적으로 막는 효과가 있다. 이런 이유로 data privacy를 위해 클로저를 사용하는 경우가 많다고 한다. 구체적인 예시는 MDN을 참고하면 될 것 같다.

정리

클로저의 정의와 특징을 정리해봤다. 클로저를 이용해서 리액트의 Hook을 모방하는 유명한 글이 있어 링크를 남겨둔다. 클로저 자체나 리액트 hook에 대한 이해에 큰 도움이 된다.