티스토리 뷰

728x90
반응형

클로저(closure)

자바스크립트 완벽 가이드 참고 및 정리

함수 객체와 함수의 변수가 해석되는 범위(변수 바인딩의 집합)의 조합은 컴퓨터 과학문헌에서 
클로저(closure)라고 일컫는다.

- 이는 함수의 변수가 유효범위 체인에 바인딩되어 있고 따라서 그 함수는 함수의 변수에 따라 닫힌다는 뜻에서 유래한 용어다.

-함수 내에서 선언된 변수는 보통 함수의 실행이 끝나면 같이 소멸해야 한다. 
하지만 함수의 실행이 끝나더라도 유효범위 체인상에서 함수 내의 변수가 계속 살아있어야 하는 
상황이라면 내부 변수가 살아있으므로 그 함수는 닫힐 수 없다. 
해당 함수가 완전히 닫힐 수 있는 경우는 함수 내에서 정의한 변수들을 참조하는 곳이 없어야 하는 상황이므로 함수의 닫힘 가능 여부는 해당 함수의 실행 종료 여부가 아니라 변수 유효 여부에 달린 것이다.
 

기술적으로 모든 자바스크립트 함수는 클로저인데, 함수는 객체이고 함수 자신과 관련된 
유효범위 체인을 가지고 있기 때문이다.
가장 일반적인 경우는 어떤 함수가 그 함수 내부에서 정의한 중첩 함수를 반환하는 것이다.

var scope="global scope"; //전역변수
function checkscope(){
  var scope="local scope"; //지역변수
  function f(){  
          return scope;   //이 유효범위에 있는 값을 반환한다.
  }
  return f();
}

checkscope();   //"local scope"
checkscope()함수는 지역 변수를 선언한 다음, 그 지역 변수를 반환하는 함수를 정의하고 호출한다.
checkscope()가  "local scope"를 반환하는것은 명백하다.
약간 바꾸어 보자
var scope="global scope";    //전역변수
function checkscope(){
      var scope="local scope"; //지역변수
      
      function f(){   
                return scope; // 이 유효범위에 있는 값을 반환한다.
      }
      return f;
}
checkscope()();  // 어떤 반환값이 올까?

중첩 함수 f()가 정의된 유효범위 체인에서 변수 scope는 "local scope"로 바인드되어 있다.

f가 어디서 호출되든 상관없이, f가 실행될 때 바인딩은 항상 유효하다.

그렇기에 "global scope"가 아니라 "local scope"를 반환한다.

클로저의 의외스럽고 강력한 특성이다. 


클로저는 자신을 정의한 바깥쪽 함수에 바인딩된 지역 변수( 그리고 전달인자)를 포함한다.

클로저는 함수의 지역변수를 포착하고 이 변수들을 내부 상태로 사용할 수 있다.

//함수를 정의하고 바로 호출한다.
var uniqueInteger=( function(){
       var counter=0;
       return function(){ return counter++; };
}() );


함수를 정의하고 호출하는 것이며 함수의 반환 결과가 uniqueInteger변수에 할당된다.

이제 함수의 본문을 살펴보면 함수의 반환 값은 또 다른 함수임을 알 수 있다.

중첩함수는 유효범위에 있는 변수에 접근하고, 바깥쪽 함수에 정의된 counter변수를 볼 수 없다.

오직 안쪽 함수만 단독으로 counter변수에 접근할 수 있을 뿐이다.

counter와 같은 내부 변수는 하나의 클로저에만 종속될 필요는 없다.

중첩 함수가 두개 이상이라고 해도 같은 바깥쪽 함수에 정의되고 같은 유효범위 체인을 공유한다면 

얼마든지 공유가 가능하다.

function counter(){
     var n=0;
     return{
           count: function(){ return n++; },
           reset:  function(){ n=0; }
     };
}

var c=counter(), d=counter(); //두 개의 카운터를 생성한다.
c.count()     //0
d.count()    //0   이들은 서로 독립적이다
c.reset()    // reset()메서드와 count()메서드는 상태를 공유한다.
c.count()   //0  c.reset()함수로 리셋했기 때문
d.count()   // 1  d는 리셋되지 않음

counter()함수는 카운터 객체를 반환한다. 이 객체는 두 메서드를 가지고 있는 count()는 그 다음에 올 정수를 반환하고

reset()은 내부 상태를 재설정한다. 먼저 이해해야 할 것은 두 메서드가 내부 변수 n을 공유한다는 것이다.


그 다음 이해해야 할 것은 counter()를 호출할 때마다 새로운 유효범위 체인과 새로운 내부 변수를 생성된다는 점이다.

따라서 counter()를 두번 호출하면, 서로 다른 내부 변수를 가진 두개의 counter객체를 얻는다.

한 카운터 객체에서 count()나 reset()을 호출하는 것은 다른 카운터 객체에는 아무 영향을 주지 않는다.


다음 예제는 counter()함수를 변형한 것으로 내부 상태를 다루는 데 일반 객체 프로퍼티 대신 클로저를 사용한다.


function counter(n){
     return {
         get count(){ 
              return console.log( n++ ); n++;  
         },
         set count(m){
              if(m>=n){
                  n=m;
                  console.log( n )
              }else{
                  throw Error( "count는 오직 더 큰값으로만 설정될 수 있습니다"); 
              }
         }
     };
}
var c=counter(1000);
c.count           //1000
c.count          //1001
c.count=2000
c.count           //2000
c.count=2000   //에러

이 버전의 counter()함수는 지역 변수를 정의하지 않지만 프로퍼티 접근 메서드들이 공유하는 내부 상태를 

보관하기 위해 매개 변수 n을 사용한다.

이로써 counter()를 호출하는 쪽에서 내부 변수의 초기 값을 지정할 수 있다.

여기까지 내부 상태를 공유하는 클로저의 일반적인 예이다.

//클로저를 사용하는 내부 프로퍼티 접근 메서드- 즉 같은 유효범위 체인에 정의된 두 클로저가 같은 내부 변수에 대한 접근 //getter/setter 메서드는 이 함수 내부에 지역적으로 정의되기 때문에 이 함수의 지역변수에 접근할 수 있다. //value변수는 선언한 두개의 메서드 (get, set)전용이고 //setter메서드를 통하지 않고서는 설정되거나 수정될 수 없다는 뜻이다. function addPrivateProperty( o, name, predicate ){ var value; //이것은 프로퍼티 값이다. //getter메서드는 value를 반화 o["get"+name]=function(){ return value; }; //setter메서드는 value를 저장하거나 //단정 함수가 값을 적합하지 않다고 판단하면 예외를 발생시킨다. o["set"+name]=function( v ){ if( predicate && !predicate(v) ){ throw Error( "set"+name+": 유효하지 않은 값 "+v ); }else{ value=v; } } } //다음 코드 addPrivateProperty() 메서드를 사용하는 방법을 보여준다. var o={} //빈 객체 //프로퍼티 접근 메서드 getName과 setName()를 추가한다. //setName이 문자열 값만 허용하는지 확인한다. addPrivateProperty( o, "Name", function(x){ return typeof x==="string"; } ); o.setName("Frank"); //프로퍼티 값을 설정한다. console.log(o.getName()); //프로퍼티 값을 얻는다. o.setName(0); //틀린 형식의 값을 설정해 본다.




클로저들이 공유해서는 안되는 변수를 공유하는 잘못된 방법.

-자주하는 실수:반복문안에서 클로저 만들기

//이 함수는 항상v를 반환하는 함수를 반환한다.
function constfunc(v){ return function(){ return v; }; }

//상수 함수에 대한 배열을 생성한다.
var funcs=[];
for( var i=0; i<10; i++) funcs[i]=constfunc(i);

//배열 요소 5의 함수는 값 5를 반환한다.
funcs[5]()  // 5

이렇게 루프를 사용하여 여러 개의 클로저를 생성하는 코드를 사용할 때 클로저를 정의하는 함수 내에서 
루프를 이동하는 것은 흔히 볼 수 있는 실수다.

//0-9값을 반환하는 함수들의 배열을 반환한다.
function constfuncs(){
      var funcs=[];
      for( var i=0; i<10; i++){
           funcs[i]=function(){ return i; };
      }
      return funcs;
}

var funcs=constfuncs();
funcs[5]()  //무엇이 반환될까?


앞 코드는  열개의 클로저를 생성하고, 생성한 클로저들을 배열에 저장한다.


모든 클로저는 같은 함수 호출 내에서 정의되고, 따라서 클로저들은 변수i에 대한 접근을 공유한다.

constfuncs() 실행이 끝나면 변수 i의 값은 10이고, 열개의 클로저 모두 이 값을 공유한다.

따라서 반환된 배열에 있는 모든 함수들은 같은 값을 반환한다.

클로저와 연관된 유효범위 체인이 "살아있다"는 사실을 기억해야 한다.


중첩함수는 유효범위에 대한 내부 사본이나 변수 바인딩과 관련한 고정된 스냅샷을 만들지 않는다.

클로저를 작성할 때 기억해야 할 또 다른 사항은 this가 자바스크립트 키워드이지 변수가 아니라는 점이다.

모든 함수 호출에는 this값이 있고, 바깥쪽 함수가 this값을 별도의 변수로 저장하지 않으면 클로저는

바깥쪽 함수의 this값에 접근 할 수 없다.


arguments바인딩 또한 비슷하다. arguments는 키워드가 아니지만 모든 함수 호출에 자동으로 선언된다.

클로저 함수는 자신만의 arguments를 가지고 있기때문에 바깥쪽 함수가 인자 배열을 

다른 이름의 변수에 저장하지 않는한 클로저는 바깥쪽 함수의 인자 배열에 접근할 수 없다.



모질라에서 제공하는 좀 더 알기 쉬운 클로저에 대한 설명이 아래 링크에서 확인할 수 있다.


728x90
반응형

'Programming language > javascript' 카테고리의 다른 글

함수1  (0) 2013.02.06
바인딩이란?  (0) 2013.02.06
가변길이 전달 인자 목록 : Arguments객체  (0) 2013.02.06
자바스크립트에서의 this란~  (0) 2013.02.06
리터럴과 생성자 사용법~  (0) 2013.02.06
댓글