Hello World

어린왕자와 여우 : javascript Closure 본문

Javascript/Tips

어린왕자와 여우 : javascript Closure

EnterKey 2012. 9. 3. 19:36
반응형

 

O'Reilly의 JavaScirpt : The Definitive Guide 5/E(그 유명한 코뿔소책)를 보면 이런 구절이 나오길래,


클로저는 흥미롭고 또한 강력한 프로그래밍 기법이다. (중략) 만약 여러분이 클로저를 이해한다면 여러분은 유효 범위 체인과 함수의 호출 객체를 이해한 것이며, 스스로를 고급 자바스크립트 프로그래머라 부를 수 있다.

(중략)


무엇보다도 날 헷갈리게 했던건,,, 클로져에 대한 모두 다른 정의

 

-다른 함수내에서 내부객체로 생성된 함수 리터럴을 반환하여 호출 프로그램에서 이를 변수로 배정한 것이다.

-클로저란 생성시 환경의 레퍼런스를 그대로 가지고 있는 것이다.

-실행될 코드와 이 함수가 실행될 유효범위의 조합이다.(O'Reilly JavaScirpt : The Definitive Guide 5/E)

 

대 혼란.

 

구글링을 수십개의 내용을 읽어 본 결과, 클로져라는 개념 자체가 복합적이기 때문에 특정한 정의를 내리기가 쉽지 않은 것으로 생각되지만 내 생각으로 가장 그 핵심을 표현하고 있는 구절은 Wikipedia article 의 첫 문장이다.

closure (also lexical closure, function closure or function value) is a function together with a referencing environment for the non-local variables of that function.

 

클로져는 그 함수의 지역 변수가 아닌 것들을 위한 참조 환경을 함께 가지고 있는 함수(실행 코드) 이다. 

이때 참조 환경이라는건 위에서 O'Reilly JavaScirpt : The Definitive Guide 5/E에서 클로져의 정의를 내릴 때 사용한 유효범위라는 말과 동일 하다. 두 정의에 의해서 JavaScript의 모든 함수가 클로져라는 말은, 즉 자바스크립트의 모든 함수는 그 함수의 지역 변수가 아닌 것들을 위한 참조 환경을 함께 가진다는 의미이다.  

 

이는 아래와 같은 JavaScript의 함수 중첩을 통해 알 수 있다.


  1. var z = "global";
  2. function outerFn()
  1. {
  1.         var z = "local";
  1.         function innerFn()
  1.         {
  1.                 alert(z);
  1.         }
  1.         innerFn();
  1. }
  1. outerFn(); //"local" 출력

 

전역 변수 상에서 z가 "global"로 정의 되어 있음에도 불구하고, "local"이 출력되는 이유는 클로져 innerFn(다시한번, JavaScript의 모든 함수는 클로져이다.)의 유효 범위 체인 상에서는 innerFn을 호출한 outerFn이 어휘적인(Lexical) 유효범위로서 포함되며, 전역 객체들(가령 전역 "global"값이 지정된 z 전역 변수) 보다 우선시 되기 때문이다. 만약 위의 코드에서 4번째 줄이 없었다면, 실행의 결과로 "global"이 출력 될 것이다. 

 

클로져의 가장 핵심적인 개념은 어휘적인(Lexical) 유효 범위다. 함수가 실행될 때는 함수가 실행되는 위치가 아닌, 함수가 정의 될 당시의 유효범위에서 실행된다.

 

어휘적인 유효 범위라는 것은 바로 아래의 예를 보면 알 수 있다. 중첩된 함수가 밖으로 꺼내어질(export) 때 어휘적인 유효 범위로 인한 가장 명확한 특성이 발생한다.

 
  1. function outerFn(outerText)
  1. {
  1.         var innerText = "first" + outerText ;
  1.         return function() {return innerText};
  1. }
  1. var execution = outerFn("second");
  2. var result = execution();

 

function outerFn은 실행 되면, function() {return innerText}라는 함수를 리턴한다. 즉 위의 코드에서 6번줄을 틍해 execution이라는 변수에는 function() {return innerText}라는 함수가 할당 되는 것이다.

 

그렇다면 위의 코드를 실행하면 어떤 결과가 나타날까?

얼핏 생각했을 때, innerText라는 변수는 outerFn 함수 내에서만 존재하는 변수이다. 따라서 6번에서 outerFn의 실행이 끝나고 7번 행에서 innerText를 return하는 함수를 실행 할 때에는 해당 변수가 존재하지 않을 것이다. 때문에 7번행에서 존재하지 않는 변수를 리턴하려 하면서 "undefined"예외가 발생 할 것 같다. 하지만 실제로는 "first second"라는 값이 result에 할당되며 코드의 실행이 끝난다.

 

왜 일까?

앞서 언급한 어휘적인(Lexical) 유효 범위가 답이다. JavaScript에서 함수가 실행될 때는 함수가 실행되는 위치가 아닌, 함수가 정의 될 당시의 유효범위에서 실행된다. 즉 execution();을 통해  function() {return innerText} 함수가 7번행 실행되더라도  function() {return innerText} 함수가 정의되었던 위치(4번행)에서는 "first second"라는 innerText 변수 값이 존재하기 때문에 result에 해당 값이 할당 되는 것이다.

 

클로져를 어렵고 까다롭게 만드는 속성은 바로 이런것이다. 브라우져의 가비지 컬렉터는 보통 함수의 로컬 변수의 경우 함수의 실행이 끝나는 시점에 메모리를 해제 해버리지만, 클로져의 속성을 유지하기 위해(함수가 정의 될 당시의 유효범위를 보존하기 위해) 위와 같이 로컬 변수를 사용하는 함수가 밖으로 내보내질 경우 메모리를 해제하지 않고 해당 값을 유지한다. 위의 execution이라는 변수에 할당된 함수내의 innerText 변수는 이렇게 해제되지 않은 메모리 상의 값을 계속해서 참조하게 된다.

 

이러한 클로져의 특성을 이용하면 함수가 실행되고나서 사라져 버리는 함수의 지역변수의 한계를 극복하여, 함수 호출의 경계를 넘어서도록 지역변수를 확장하여 사용하는 기법들이 가능하다. 이것이 클로져를 강력한 프로그래밍 테크닉이라고 부르는 이유라고 생각된다.(John Resig의 타이머 예시이벤트 리스너 예시)

 

하루 정도 여러 예시코드를 찾아보며 공부한 결론이지만, 사실 아직도 뜬구름 잡는 정도의 개념이해에 머무르고 있다는 생각이 든다. 클로저 특성을 응용하여 코드를 짜는 연습이 필요하다.

출처 : http://www.choyoungil.pe.kr/63

반응형
Comments