Language/JS(Node.js)

[Javascript] 클로저란 무엇인가?

Joonfluence 2021. 12. 30.

서론

대상독자

일급함수, 렉시컬 스코프 등의 자바스크립트 개념을 알고 있는 웹 개발자

학습목표

클로저란 무엇인지 이해하고 알맞은 상황에 클로저를 활용하여 문제를 해결할 수 있다.

본론

클로저의 정의

클로저는 자바스크립트 개발자라면 한 번 이상은 들어봤을 개념일텐데요, 함수형 프로그래밍 언어인 스칼라(Scala), 하스켈(Haskell), 얼랭(Erlang) 등 에서도 활용될 수 있는 범용적인 개념입니다.

클로저란 함수와 그 함수가 선언된 렉시컬 환경과의 조합을 말합니다 - MDN. 정의만으로는 설명이 잘 와닿지 않을 것입니다. 그래서 내용을 좀 더 세분화해보겠습니다.

렉시컬 환경이란?

렉시컬 환경이란 무엇일까요? 렉시컬 환경은 다음의 3가지 정보를 기록하는 자료구조입니다.

1) 식별자 : 변수나 함수, 클래스 등
2) 바인딩된 값 : this
3) 상위 스코프에 대한 참조

띠라서 렉시컬 환경을 통해, 전역 혹은 함수 스코프 아래에서 상위 스코프는 무엇인지, 해당 스코프 아래의 변수는 무엇이 있는지, this가 가리키는 것은 무엇인지 등의 정보들을 알 수 있게 됩니다. 이번에는 예제와 함께 다시 클로저에 대한 설명으로 돌아가보도록 하겠습니다.

const x = 1;

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

}

const innerFunc = outer(); // ③
innerFunc(); // ④ 10

① 외부 함수(outer) 안에 중첩 함수(inner)가 정의되어 있으며, 외부 함수를 호출하면 내부 함수를 반환합니다.
② 중첩 함수에선 외부 함수 내에 정의된 변수 x를 불러오고 있습니다.
③ outer 함수가 실행되고 반환값이 innerFunc라는 변수에 값으로 할당됩니다.

이 때, outer 함수는 호출된 즉시 실행 컨텍스트 스택에서 pop 됩니다. 곧, 외부 함수는 실행 컨텍스트에서 사라지게 됨으로써 생명 주기를 다하게 됩니다. 그 결과, 외부 함수 내에 있는 내부 변수 x에 접근할 수 있는 방법이 사라지게 됩니다. 그러나 중첩 함수에서는 여전히 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있는데요, 이러한 상황에서 활용되는 중첩 함수를 클로저라고 합니다. 이로써 x라는 변수는 클로저 함수를 통해서만 변경될 수 있게 됩니다.
그렇다면 이처럼 복잡한 구조를 활용하여 값을 참조하는 까닭은 무엇일까요? 이는 예제를 통해, 클로저 함수는 왜 이렇게 복잡해질 수 밖에 없었는지 좀 더 자세히 설명하겠습니다.

클로저를 쓰는 이유

결론부터 말씀 드리겠습니다. 클로저를 사용하는 이유는 해당 값에 대한 접근을 특정 함수로 제한함으로써, 해당 값을 안전하게 보관하기 위함.입니다. 꼭 클로져를 써야 만 하냐구요? 이는 예제를 통해, 조금 더 설명 드리겠습니다.

let num = 0;

const increase = function () {
  return ++num;
};

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3

클로저를 사용하지 않고 비슷한 방식으로 값을 변경한다고 해봅시다. 아래와 같이, 전역변수를 선언해서 값을 관리할 수 있을 것입니다. 그러나 이는 위험합니다. 전역변수는 언제든 다른 곳에서 참조될 수 있기 때문에, 변경될 위험에 항상 노출되어 있기 때문이죠.

const increase = function () {
  let num = 0;
  return ++num;
};

console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1

전역변수 대신, 지역변수를 선언한다면 값을 계속 보관할 수 있을까요? 그렇지 못합니다. increase 함수가 호출될 때마다 num 값은 0으로 초기화되기 때문에 이전 값이 유지되지 못하죠. 결국 앞선 예제에서 보았듯, 클로저를 활용해야 값을 의도된 방식대로 유지하고 보관할 수 있다는 결론이 나게 됩니다.

클로저의 조건

1) 중첩 함수는 상위 스코프의 식별자를 참조하여야 한다.
2) 식별자는 클로저 함수가 호출되기 전까지 변경되지 않고 유지되어야 한다.
3) 식별자는 해당 클로저 함수만이 변경할 수 있어야 한다.

function foo() {
  let x = 1;

  // 일반적으로 클로저라고 하지 않는다.
  // bar 함수는 클로저였지만 곧바로 소멸한다.
  function bar() {
    // 상위 스코프의 식별자를 참조한다.
    console.log(++x);
  }
  bar();
}

foo(); // 2
foo(); // 2
foo(); // 2
foo(); // 2

추가로 상위 스코프를 참조하지만, 클로저 함수의 상위 스코프에 해당하는 함수보다 실행 컨텍스트가 먼저 소멸될 경우 값이 유지되지 않습니다.

마무리

오늘은 클로저에 대해서 알아보았습니다. 복잡하다 느껴지신다면, 아래 코멘트를 통해 질문을 남겨주시면 언제든지 답변해드리도록 하겠습니다. 오늘도 읽어주셔서 감사합니다.

반응형

댓글