블로그 이미지
초딩입맛제주아재
하고 싶은 것만 하며 살고 싶다

calendar

1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
2007. 9. 21. 13:56 Programing/HTML/JavaScript/CSS
클로저(closure)는 자유로운(구속되지 않은) 변수들을 포함하고 있는 코드 블록이다. 이러한 변수들은 코드 블록이나 글로벌 콘텍스트에서 정의되지 않고, 코드 블록이 정의된 환경에서 정의된다. "클로저"라는 명칭은 실행할 코드 블록(자유 변수의 관점에서, 변수 레퍼런스와 관련하여 폐쇄적이지 않은)과 자유 변수들에 대한 바인딩을 제공하는 평가 환경(범위)의 결합에서 탄생한 것이다. 클로저 지원의 다양함은 Scheme, Common Lisp, Smalltalk, Groovy, JavaScript, Ruby, Python에서 찾아볼 수 있다.

클로저의 가치는 함수 객체(function objects) 또는 익명 함수(anonymous functions)로서 작용하고, 유형 시스템(type system)이 데이터뿐만 아니라 코드도 나타낼 수 있어야 한다는 점에서 유형 시스템에 대한 결과도 갖고 있다. 클로저가 있는 대부분의 언어들은 함수들을 퍼스트-클래스 객체들로서 지원하는데, 함수들은 변수에 저장될 수 있고, 매개변수로서 다른 함수들에 저장되며, 동적으로 생성되고, 함수들에서 리턴된다.

- IBM developer works 인용 -


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

다음 javascript 코드를 보며 설명하겠다.

<script type="text/javascript">
var example = function(){
    var i = 1;
    return function(){
        alert(i++);
    };
}();
</script>

<input type="button" onclick="example()" value="click" />

'click' 버튼을 클릭하면 계속 숫자가 증가하게 된다.
분명 i 라는 변수는 함수에서 선언된 지역 변수이다.
this.i 라고 객체 변수로 선언 했다면 쉽게 이해가 되겠지만 분명 지역변수로 선언되었다.
때문에 버튼을 클릭할때마다 i의 값은 매번 1이 되어야 한다.
하지만 계속 i는 증가한다.

그 이유가 바로 클로저에있다.

javascript 는 클로저를 지원하는 언어이기 때문에 return function(){} 이 선언되는 시점의 환경, 즉 지역변수 i의 레퍼런스를 가지고 있기 때문에 계속해서 i를 증가 시킬 수 있다.

어떻게 보면 상당히 난해한 개념이기도 하다.
하지만 간단한 원리-생성시의 환경 정보를 그대로 가지고 있다-만 파악한다면 쉽게 클로저를 사용 할 수 있을 것이다.


참고자료



posted by 초딩입맛제주아재
2007. 8. 7. 01:35 Programing/HTML/JavaScript/CSS

<script type="text/javascript">
function StopWatch(){
    this.now = new Date();
    this.counter = 0;
    this.timer = null;
}

StopWatch.prototype.start = function(){
    this.timer = window.setInterval(this.counterUp,1000);
}

StopWatch.prototype.counterUp = function(){
    this.counter++;
}

StopWatch.prototype.stop = function(){
    window.clearInterval(this.timer);
    this.timer = null;
    alert(this.counter);
}

var sw = new StopWatch();
</script>

<input type="button" onclick="sw.start()" value="start" />
<input type="button" onclick="sw.stop()" value="stop" />

위에 간단한 스톱워치 객체가 있다.
객체를 생성하고 start()메서드를 호출하면 스톱워치가 작동하고 stop()메서드를 호출하면 스톱워치가 멈추고 경과된 시간을 초단위로 알려준다.

한번 스톱워치를 작동시켜보자.

start 버튼을 클릭하고 잠시 기다렸다 stop 버튼을 클릭해보자.
예상 되는 결과는 화면에 alert창이 뜨고 경과된 시간이 출력이 되는것이다.

결과가 어떠한가?
경과된 시간이 출력 됐는가?

아마도 alert 창에는 0이 출력되었을 것이다.
왜 이런 결과가 나왔을까?
코드가 잘못됐나 싶어 코드를 훑어봐도 도무지 이유를 찾을 수가 없다.
휴~

한숨은 그만 쉬고 차근차근 디버깅을 해보자.
개발자에게 디버깅은 밥 한술 먹고 반찬 한젓가락 떠 먹는것과 똑같지 않은가;;;

결과를 보면 스톱워치 객체의 counter 속성이 변하지 않았다는것을 알 수 있다.

this.counter++;

이 코드는 분명 this=StopWatch 의 인스턴스 변수 counter 를 1증가 시키는 코드다.
그런데 증가가 되지 않았다.
그렇다면 counterUp 메서드가 호출이 되지 않은 것인가?
counterUp 메서드가 제대로 호출이 되는지 확인하기 위해 counterUp메서드에 alert코드를 삽입한 후 다시 스톱워치를 작동시켜보자.

StopWatch.prototype.counterUp = function(){
    this.counter++;
    alert(this.counter);
}


NaN이라는 메세지를 확인했는가?
counter 라는 인스턴스 변수를 인식하지 못하고 있다.

바로 this가 StopWatch 가 아니라는 뜻이다.

그렇다면 우리를 혼란 속으로 빠뜨린 이 this의 정체는 무엇일까?

counterUp 메서드를 다음과 같이 고쳐보자

StopWatch.prototype.counterUp = function(){
    var str = '';
    for(var temp in this){
        str += temp + '\n';
    }
    alert(str);
}

경고창의 내용을 보면 뭔가 낯익은 단어들이 보일 것잉다.
location,opener,status 등등...
감이 오는가?

counterUp 메서드 안에서의 this의 정체는 바로 window객체였던 것이다.

우리는 분명 StopWatch 객체를 생성하고 StopWatch의 메서드인 start 와 counterUp, stop 메서드를 사용했다.
그런데 별안간 window객체라니...

window객체가 출몰하게된 자세한 이유는 여기를 참조하기 바란다.

요약하자면 setInterval 함수에 인자로 넘긴 StopWatch의 counterUp 메서드의 코드는 전달이 되지만 counterUp의 인스턴스는 전달이 되지 않는 다는 것이다.
이 부분에서 '클로저' 에 대해서 한번 검색을 해볼 것을 권한다.(Ajax In Action을 봤다면 대충 알겠지만..)

때문에 setInterval 에 의해 반복 호출 되는 counterUp는 window객체에 귀속 되게 되고
결국 this는 window객체를 가리키게 되는 것이다.

다소 설명이 애매모호하니 위의 링크를 정독 할 것을 권한다.(내 영어실력이 엄청 짧다....ㅡㅡ;)

아무튼 이제 문제는 찾았다.
this가 window 객체를 가리킨다는 것.

그렇다면 어떤 방법으로 이 문제를 해결 해야 할까?
카운터 증가를 담당하는 메서드의 주인이 window 로 넘어가 버렸으니 StopWatch의 counter 는 무의미하다.
대신 window 객체에 counter 를 달아주면 어떨까??

window.counter = 0;

요걸 start 메서드에 한번 넣어 보자.

StopWatch.prototype.start = function(){
    window.counter = this.counter;
    this.timer = window.setInterval(this.counterUp,1000);
}

그리고 stop에서 원래상태로 되돌려주자.

StopWatch.prototype.stop = function(){
    window.clearInterval(this.timer);
    this.timer = null;
    this.counter = window.counter;
    alert(this.counter);
}

자 이제 다시 스톱워치를 작동시켜 보자.

0이 아닌 다른 숫자가 보이지 않는가??

window객체에 임의로 다른 속성을 할당하는게 꺼림찍 하다면 다른 방법을 쓸 수 있다.
위에서 검색해 보길 권했던 '클로저'를 이용 하는 방법이다.

이 방법은 코드를 좀 많이 고쳐야 한다.

function StopWatch(){
    this.now = new Date();
    var counter = 0;
    this.timer = null;

    this.counterUp = function(){
        counter++;
    }

    this.stop = function(){
        window.clearInterval(this.timer);
        this.timer = null;
        alert(counter);
    }
}

StopWatch.prototype.start = function(){
    this.timer = window.setInterval(this.counterUp,1000);
}

counter변수가 인스턴스 변수가 아닌 임시 변수로 바뀌었고
counterUp과 stop 메서드가 StopWatch 객체안으로 들어왔다.

위와 같이 StopWatch 함수 안에서 메서드를 정의 하면 클로저라는 것이 자동으로 생성 되어 counter 라는 변수와 counterUp과 start 메서드간에 연결 고리가 만들어진다.
때문에 비록 지역변수인 counter 라도 StopWatch 객체가 살아 있는한 StopWatch 객체와 그 생명을 함께 하게 된다.

마무리를 해야 하는데 결론을 내기가 힘들다.
좀 복잡한 내용이고 나도 대충 이해만 한것을 풀어 쓰려니 읽는 사람들도 이해하기가 힘들 것 같다.
중간 내용은 다 잊어 버리고 처음과 끝만 보는게 그나마 이해하는데 도움이 될지도 모르겠다. ㅡㅡ;

좀더 자세한 설명을 원하는 분은 위에 링크된 MDC(Mozila Developer Center)와 Ajax in Action(부록B)를 꼭 읽어 보시길 바라며 나는 도망가야겠다;;;

혹, 이 포스팅을 보고 이건 이게 아니라 정확히 이거다~ 라고 설명해 주실 수 있는 분은 꼭! 가르침을 주시길 부탁드리며...

 

posted by 초딩입맛제주아재
prev 1 next