클로저(Closure)에 대한 간단한 고찰

suky·2021년 9월 17일
2
post-thumbnail

안녕하세요! suky입니다. 😁
부스트캠프를 하면서 Vanilla JS에 대하여 공부하는데 이해가 안되는 것들이 많더라구요!
오늘은 그중에 Closure라는 개념을 정리해볼까 합니다.

클로저

클로저란 무엇일까?

Mozilla에 따르면 다음과 같이 나옵니다.

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

클로저를 이해하려면 Lexical Scoping을 이해해야하니 Lexical Scoping이랑 연관된 실행 컨텍스트라는 개념을 먼저 알아봅시다!

실행 컨텍스트

실행 컨텍스트란 ECMA-262 10th Edition에선 다음과 같이 정의합니다.

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.

저는 영어를 잘 하지 못해 번역기를 돌려보겠습니다 ㅋㅋ;;;

코드의 런타임 평가를 추적하는데 사용되는 사양 장치라는 것이 어떤 의미인지가 중요한데, 제가 의역한 느낌으로는 실행 중에 코드의 상태를 저장하는 스펙이라고 생각합니다.

말 그대로 상태를 저장하는 것이니 실행 컨텍스트 안에는 코드, 함수, 모듈, 상태 정보(perform, suspend, resume), 범위 등등이 저장되게 됩니다.
(쉽게 이해하기 위하여 자세한 요소는 생략했습니다 😂)
중요한 것은 범위가 저장된다는 것이죠!

범위는 [[scope]]를 통해 확인할 수 있고, [[scope]]는 새로운 컨텍스트를 만나면 기존의 컨텍스트 스코프의 자신의 스코프를 연결하는 식으로 체인 형식으로 관리됩니다.

그리고 실행 컨텍스트는 스택에 의해 관리되며 처음에는 전역 컨텍스트(브라우저는 window, node js는 global)가 위치하게 됩니다. 연결되지 않은 새로운 컨텍스트를 만나면 실행 컨텍스트 스택에 push되는 식으로 관리되고, 실행이 완료되면 pop으로 반환하는 방식입니다.

한 번 예시를 들어보겠습니다!

function foo() {
  function bar() {
    function baz() {
      console.dir(baz);
    }
    console.dir(bar);
    baz();
  }
  console.dir(foo);
  bar();
}
foo();

실행하면 실행 컨텍스트 스택의 흐름은 다음과 같습니다.

그리고 결과값은 다음과 같습니다.

foo, bar, baz 함수 [[Scopes]]에 어떤 값이 들어가있는지 볼 수 있습니다!
오 근데 Closure라는 것이 있는 것을 볼 수 있습니다.

실행 컨텍스트 내에 [[Scopes]]를 바탕으로 보았을 때 Closure는 실행 컨텍스트 스택 처럼 보입니다! 전역이 아니면 Closure인 것처럼 말이죠.
과연 그런지 확인해봅시다!

클로저

제가 정리한 바로는 클로저는 실행 당시의 상태를 기억하는 것입니다.
클로저는 렉시컬 스코프의 유효범위를 지닌다는데 이는 실행 컨텍스트의 LexicalEnvironment를 공부하시는 것을 추천드립니다.

한 예시를 하나 들어볼까요?

function foo() {
  const a = 1;
  function bar() {
    console.log(a);
  }
  return bar;
}
const barFunc = foo();
barFunc();

결과는 다음과 같습니다.

이상한 것을 눈치채신 분들 있으신가요?
분명히 const barFunc = foo();를 하는 순간 foo()의 반환이 끝나 사라졌을 것인데 foo()의 a를 그 이후의 barFunc가 반환하는 것을 이상하게 생각하는 것이 클로저의 핵심입니다!

그 이유를 console.dir(bar)를 통해 확인해봅시다.

function foo() {
	const a = 1;
  	function bar() {
    	console.log(a);
    }
  	console.dir(bar);
  	return bar;
}
const barFunc = foo();
barFunc();

console.dir(bar)를 보면 [[Scopes]]에 현재 범위 정보를 볼 수 있는 것을 확인 할 수 있습니다.
foo는 분명히 반환되어 사라졌어야하지만 Closure라는 상태로 foo의 값이 저장되어 있음을 확인할 수 있습니다.

클로저의 응용

자유변수란?

위의 barFunc를 실행시켰을 때 foo는 반환되어 사라졌어야 하지만 클로저에 의해 상태가 기억되고 있고, 이 때 foo의 a는 barFunc에 의해서 접근할 수 있습니다.
이렇듯 함수는 반환되었지만 클로저에 의해 기억되고 있고, 클로저에 의해서만 접근할 수 있는 변수를 자유 변수라고 합니다.
자유 변수를 이용하여 클로저를 응용할 수 있는 예제를 준비했습니다.

IIFE (Immediately Invoked Function Expression)

IIFE는 정의되자마자 즉시 실행하는 함수입니다.
이를 이용해서 원하는 변수를 캡슐화할 수 있습니다.
밑에 예제에서 a 변수를 접근하는 방법은 오로지 plus, minus, print 뿐입니다.

const foo = (function () {
  let a = 0;
  function plus() {
    a += 1;
  }
  function minus() {
    a -= 1;
  }
  function print() {
    console.log(a);
  }
  return {
    plus, minus, print
  };
})();

foo.plus();
foo.print();
foo.minus();
foo.print();

싱글톤 패턴

자유 변수와 IIFE를 이용한다면 싱글톤 패턴을 만들 수 있습니다.
물론 최신 문법에서는 class 내부에서 싱글톤 처리를 하기도 하지만요! 😂

class foo {}
  
const singleton = (function () {
  let instance = null;
  return {
    getInstance: function () {
      if (!instance) {
        instance = new foo();
      }
      return instance;
    }
  }
})();

const a = singleton.getInstance();
const b = singleton.getInstance();
console.log(a === b);

모질라 공식 예제

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures에는 Mozilla에서 제공하는 다양한 공식 예제가 더 있습니다.

마무리

클로저에 대하여 정리를 해보았지만 부족한 점이 많을 수 있습니다 ㅠㅠ...
피드백 환영합니다 😎

profile
도전하고 성장하는 개발자입니다 :)

1개의 댓글

comment-user-thumbnail
2021년 10월 13일

와 잘 보고 갑니다!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

답글 달기