본문 바로가기

javascript Deep Dive

[JavaScript] 클로저(Closure)

클로저에 대한 MDN의 정의

 

" 클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경과의 조합이다."

무슨 의미인지 잘 와닿지 않는다. 위 정의에서 중요한 키워드는 "함수가 선언됐을 때의 렉시컬환경이다."

 

function outerFunc() {
	var x = 10;
    var innerFunc = function () { console.log(x); };
    innerFunc();
}

outerFunc(); // 10

 

함수 outerFunc 내에서 내부함수 innerFunc가 선언되고 호출되었다. 이때 내부함수 innerFunc는 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있다. 이는 함수 innerFunc가 함수 outerFunc 내부에 선언되었기 때문이다.

 

더보기

스코프는 함수를 호출할 때가 아니라 함수를 어디에 선언하였는지에 따라 결정된다. 이를 렉시컬 스코핑이라고 한다. 위 예제의 함수 innerFunc는 함수 outerFunc의 내부에서 선언되었기 때문에 함수 innerFunc의 상위 스코프는 함수 outerFunc이다. 함수 innerFunc가 전역에 선언되었다면 함수 innerFunc의 상위 스코프는 전역 스코프가 된다.

함수 innerFunc가 함수 outerFunc의 내부에 선언된 내부함수이므로 함수 innerFunc는 자신이 속한 렉시컬 스코프(전역, 함수 outerFunc, 자신의 스코프)를 참조할 수 있다. 이것을 실행 컨텍스트의 관점에서 설명해보자.

 

내부함수 innerFunc가 호출되면 자신의 실행 컨텍스트가 실행 컨텍스트 스택에 쌓이고 변수 객체와 스코프 체인 그리고 this에 바인딩할 객체가 결정된다. 이때 스코프 체인은 전역 스코프를 가리키는 전역 객체와 함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체 그리고 함수 자신의 스코프를 가리키는 활성 객체를 순차적으로 바인딩한다. 스코프 체인이 바인딩한 객체가 바로 렉시컬 스코프의 실체이다.

 

내부함수 innerFunc가 자신을 포함하고 있는 외부함수 outerFunc의 변수 x에 접근할 수 있는것, 다시 말해 상위 스코프에 접근할 수 있는 것은 렉시컬 스코프의 레퍼런스를 차례대로 저장하고 있는 실행 컨텍스트의 스코프체인을 자바스크립트 엔진이 검색하였기에 가능한 것이다.자세하게 설명하면 아래와같다.

 

1. innerFunc 함수 스코프(함수 자신의 스코프를 가리키는 활성 객체) 내에서 변수 x를 검색한다. 검색이 실패

2. innerFunc 함수를 포함하는 외부 함수 outerFunc의 스코프(함수 outerFunc의 스코프를 가리키는 함수 outerFunc의 활성 객체) 에서 변수 x를 검색한다. 검색이 성공한다.

 

내부함수 innerFunc를 함수 outerFunc 내에서 호출하는 것이 아니라 반환하도록 변경.

function outerFunc() {
  var x = 10;
  var innerFunc = function () { console.log(x); };
  return innerFunc;
}

/**
 *  함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
 *  그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
 */
var inner = outerFunc();
inner(); // 10

함수 outerFunc는 내부함수 innerFunc를 반환하고 생을 마감했다. 즉, 함수 outerFunc는 실행된 이후 콜스택(실행 컨텍스트 스택)에서 제거되었으므로 함수 outerFunc의 변수 x 또한 더이상 유효하지 않게 되어 변수 x에 접근할 수 있는 방법은 달리 없어 보인다. 그러나 위 코드의 실행 결과는 변수 x의 값인 10이다. 이미 life-cycle이 종료되어 실행 컨텍스트 스택에서 제거된 함수 outerFunc의 지역변수 x가 다시 부활이라도 한 듯이 동작하고 있다. 뭔가 특별한 일이 일어나고 있는 것 같다.

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

 

순서로보면 아래와 같다.

1. outerFunc가 콜스택에 할당되고 실행되어 제거된다.

2. outerFunc가 콜스택에서 제거되었어도 innerFunc가 호출된다 outerFunc의 변수 x를 참조를 하면서

 

위의 현상을 보고 클로저를 정의해보자면

 

클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경인 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수이다.

 

한마디로. 클로저는 자신이 생성될때의 환경(Lexical environment)을 기억하는 함수다.

 

실행 컨텍스트 관점에서 설명하면, 내부함수가 유효한 상태에서 외부함수가 종료하여 외부함수의 실행 컨텍스트가 반환되어도, 외부함수 실행 컨텍스트 내의 활성객체(변수, 함수 선언들의 정보를 가지고 있다)는 내부함수에 의해 참조되는 한 유효하며 내부함수가 스코프 체인을 통해 참조할 수 있는 것을 의미한다.

 

클로저의 활용

1. 클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지

2. 전역 변수 사용 억제

 

3. 정보의 은닉

 

'javascript Deep Dive' 카테고리의 다른 글

[Javascript] 클래스(class)  (1) 2023.12.26
[JavaScript] prototype  (0) 2023.12.23
[JavaScript] Callback 함수  (0) 2023.12.22
[JavaScript] This  (0) 2023.12.22
[JavaScript] 실행 컨텍스트  (2) 2023.12.21