[TIL #27] Closure 란?

앞 포스트에서 다뤘던 Hoisting과 함께 JavaScript 필수 이론격으로 언급되는 Closure에 대해서 다뤄보자🏃‍♂️

Closure 란?

함수함수가 선언된 어휘적 환경(Lexical environment)조합이다.
출처 MDN

(뭐라..고요..?)

간단하게 말하자면 함수 밖에서 선언된 변수를 함수 내부에서 사용할 때 Closure가 생겨난다고 할 수 있다.

사실 Closure는 JavaScript 고유의 개념은 아니고 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.
또한 Closure를 이해하려면 Execution Context (실행 컨텍스트)에 대해 알고 있어야 하는데 자세한 내용은 따로 다루기로 하고 간단하게 말하면 다음과 같다.

Execution Context

  • 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념
  • 실행 가능한 코드가 실행되기 위해 필요한 환경
  • 실행 할 코드에 제공할 환경 정보들을 모아놓은 객체



Closure 예제

예제 코드와 함께 Closure에 대해서 정리해보자
(이 코드는 Closure가 아니다!)

function outerFunc() {
  var name = "minkyoung";
  var innerFunc = function() { console.log(name);};
  innerFunc();
}
outerFunc();	// minkyoung

외부함수 outerFunc 안에 내부함수 innerFunc이 선언되고 호출까지 되고 있는 것을 확인 할 수 있다. 이때 innerFunc은 자신을 포함하고 있는 외부함수 outerFunc의 변수인 name에 접근이 가능하다.
이게 가능한 이유는 Lexical Scoping 때문인데 Scope는 함수를 호출할 때가 아니라 함수를 어디에 선언했는지에 따라서 결정된다는 뜻이다.

내부함수 innerFunc이 호출되면 자신의 실행 컨텍스트가 실행 컨텍스트 스택에 쌓이게 되고, Variable Object(변수 객체)Scope Chain(스코프 체인), 그리고 this에 바인딩 할 객체가 결정된다.

이때 Scope Chain은 Global Scope를 가리키는 전역 객체와 함수 outerFunc의 Scope를 가리키는 함수 outerFunc의 활성 객체 그리고 함수 자신의 Scope를 가리키는 활성 객체를 순차적으로 바인딩한다. Scope Chain이 바인딩한 객체가 바로 Lexical Scope의 실체이다.

내부함수 innerFunc이 실행될 때 자신을 포함하고 있는 외부함수 outerFunc의 변수 name에 접근 할 수 있는 것, 다시말해 상위 스코프에 접근 할 수 있는 것Lexical Scope의 레퍼런스를 차례대로 저장하고 있는 실행 컨텍스트의 Scope Chain을 JavaScript 엔진이 검색했기 때문에 가능했던 것이다.

  1. innerFunc함수 Scope내에서 변수 name을 검색하지만 찾지 못한다.
  2. innerFunc함수를 포함하는 외부함수 outerFunc함수 Scope에서 변수 name을 검색, 성공한다.

위의 예제 코드를 조금 바꿔서 내부함수 innerFunc을 외부함수 outerFunc내에서 호출하는 것이 아니라 반환하도록 해보자

Closure 코드

function outerFunc() {
  var name = "minkyoung";
  var innerFunc = function() { console.log(name);};
  return innerFunc;
}

/**
* 함수 outerFunc을 호출하면 내부함수 innerFunc이 반환된다
* 함수 outerFunc의 실행 컨텍스트는 소멸된다.
*/
var inner = outerFunc();
inner();	// minkyoung

이 코드에선 함수 outerFunc은 내부함수 innerFunc을 return 하고 소멸되었다. 즉 outerFunc는 실행된 이후 실행 컨텍스트 stack에서 제거되었으므로 함수 outerFunc의 변수 name또한 유효하지 않게 되어 오류가 뜰 것 같지만 예상과는 반대로 정상적으로 출력이 되는 것을 확인 할 수 있다.

이처럼 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데 이러한 함수를 Closure 라고 부른다.

함수함수가 선언된 어휘적 환경(Lexical environment)조합이다.

이제 위에서 이해하지 못했던 이 말을 다시 본다면, 함수 = 내부함수를 의미하고 함수가 선언된 어휘적 환경 = 내부함수가 선언됐을 때의 Scope를 의미하는 것이다.

Closure는 return 된 내부함수가 자신이 선언됐을 때의 Lexical Enviroment인 Scope를 기억하여 자신(내부함수)이 선언됐을 때의 Scope 밖에서 호출되어도 그 Scope에 접근할 수 있는 함수를 의미한다.
다시말해 자신이 생성되었을 때의 환경을 기억하는 함수라고 할 수 있다.



Closure 활용

Closure가 가장 잘 사용되는 상황은 현재 상태를 기억하고, 변경된 최신 상태를 유지하는 상황이다.

<!DOCTYPE html>
<html>
  <body>
    <button class="toggle">+</button>
    <div class="box" style="width: 100px; height: 100px; background: black;"></div>
    
    <script>
      var box = document.querySelector('.box');
      var btn = documnet.querySelector('.toggle');
      
      var toggle = (function() {
      	var isOpen = false;
      	return function() {	// 1. Closure를 return
      		box.style.display = isOpen ? 'block' : 'none';
      		isOpen = !isOpen;	// 3. 상태 변경
      	};
      })();
      
      btn.onClick = toggle;	// 2. 이벤트 프로퍼티에 Closure 할당
    </script>
  </body>
</html>

위의 예제는 +라는 토글 버튼을 누르면 div가 보였다가 안보였다가 하는 코드이다.

  1. 함수는 Closure를 반환하고 바로 소멸한다. 이때 이 Closure는 변수 isOpen을 기억한다.
  2. Closure를 이벤트 핸들러로서 이벤트 프로퍼티에 할당한다. 이벤트 프로퍼티에서 Closure를 제거하지 않는 한 isOpen은 소멸하지 않는다.
  3. 버튼을 클릭하면 Closure(토글함수)가 호출되며 isOpen의 값이 변경된다. isOpen은 Closure에 의해 참조되고 있기 때문에 유효하며, 최신 상태를 계속해서 유지한다.

Closure의 경우에는 정말 많은 자료를 찾아봤지만 실행 컨텍스트에 대해서 사전에 알고 있어야 해서 이해하기가 정말 정말 정말 어려웠다..
찾다가 설명을 쉽게 해주신 분의 글을 찾아서 한번 쭉 읽고 나서 따라적고 내 머리로 이해 가능한 말로 조금씩 바꾸면서 이해하려고 노력했다 😥
실행 컨텍스트에 대해서 정리할 때 Closure를 한번 더 보고 마스터 해야겠다...

참고 자료
참고 자료

좋은 웹페이지 즐겨찾기