태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

 

* 이번에는 자바스크립트의 최대 강점이자 가장 독특한 특징 중 하나인 closure에 대해서 알아보자.

 

- 이전 글 

2012/12/10 - [속깊은 자바스크립트 강좌] 시작 (예고편) 

2012/12/17 - [속깊은 자바스크립트 강좌] 자바스크립트의 Scope와 Closure 기초

2013/01/07 - [속깊은 자바스크립트 강좌] function declaration vs function expression 차이점

2013/01/10 - [속깊은 자바스크립트 강좌] 함수를 호출하는 방법과 this의 이해

 

* 이번편을 읽기 전에 closure와 scope에 대한 기본적인 지식이 있어야하니까 아래의 이전 글을 읽고 오면 이해하는데 도움이 될 것이다.

2012/12/17 - [속깊은 자바스크립트 강좌] 자바스크립트의 Scope와 Closure 기초

 

 

* Closure!

: 지금까지 틈틈히 자바스크립트만의 다른 개발 방법에 대해서 계속 간단하게 살펴봐왔었는데, 거의 모든 방법들에 이 closure가 연관되어있다. 이러한 closure는 functional language에서 이전 글들에서 closure가 사용되었던 예들을 다시 한번 살펴보자. 아래의 소스들은 지금까지 공부해왔던 내용 중에 나왔던 소스들이다. 앞 뒤의 소스들은 간단하게 생략한 것들도 있다.

    function setDivClick(index) {
        document.getElementById("divScope" + index).addEventListener("click", 
            function () {  // #1
                alert("You clicked div #" + index);
            }, false);
    }

    for (i = 0; i < len; i++) {
        document.getElementById("divScope" + i).addEventListener("click", (function (index) {   // #1
            return function () {  // #2
                alert("You clicked div #" + index);
            };
        }(i)), false);  //#3
    }

function bind(obj, func) {
    return function () {
        func.apply(obj, arguments);
    };
}
document.getElementById("clickDiv3").addEventListener("click", bind(unikys, unikys.say));


: 위의 각 소스들은 지금까지 closure를 이용해왔던 예들이다. 위의 소스들의 공통점이 무엇인지 잘 살펴보면 closure는 어떻게 생성되는지 쉽게 이해할 수 있을 것이다. 그냥 훑어가며 봤을 때에는 약간 어려울지도 모르겠지만, 첫번째 특징은 closure는 function 안에 function이 선언될 때 생성된다는 것을 알 수 있다. 두번째 특징은 바로 function에 선언된 scope가 아닌 다른 scope에서 호출 될 때이다. 이는 비동기적으로 활용될수도 있고, function을 return 해서 사용할 때에도 적용이 가능한 것이고, 위의 이벤트 핸들러에서 활용하고 있는 것이 가장 대표적인 예이다. 그럼 먼저 closure의 특징을 살펴보자.



* Closure의 특징

: Closure가 나타나는 가장 기본적인 환경은 바로 함수 안에 함수가 다시 선언되어 호출되었을 때이다. 이는 가장 기본적인 예로 inner function과 outer function을 통해서 나타낼수 있다.

function outer () {
    var count = 0;    // #1
    var inner = function () {    // #2
        return ++count;
    };
    return inner;    // #3
}
var increase = outer();    // #4

increase();    // === 1    #5
increase();    // === 2


: 여기서 짧게 설명을 한다면 #1 count 변수는 outer의 local 변수이고 #2에서 outer의 로컬 함수로 inner 함수가 선언이 된다. 이때에 #2 안에서는 outer 함수의 count 변수가 접근 가능하다. 이러한 상태에서 inner 함수는 #3에서 outer 함수의 결과로 리턴이 된다. 이 때 #4에서 outer 함수가 호출이 되고, #3에서 리턴된 inner가 #4의 increase 변수에 저장이 되고 #5에서 리턴된 inner 함수가 호출이 되는 것이 순서다. 여기서 중요한 것은 바로 inner 함수가 리턴되면서 다른 곳에서 호출이 될 때에도 inner 함수가 선언되었던 당시에 접근 가능했던 변수 count가 계속 접근이 가능하다는 것이다. 그리고 scope의 개념으로 볼 때에 #4, #5 등 outer 함수 외부에서는 outer 함수의 local 변수인 #1의 count에 접근할 방법이 없게 된다. 즉, 자바스크립트에서도 일반적인 객체지향에서 말하는 private 개념이 적용이 가능한 것이다. 이것이 closure의 가장 기본적인 특징이고 개념이 되는 것이다. 조금 다른 예제를 살펴보자. 이번 예제는 라이브러리를 이용한다면 아주 자주 이용할 패턴이다.


function outer () {
    var count = 0;
    return {    // #1
        increase: function () {
            return ++count;
        },
        decrease: function () {
            return --count;
        }
    };
}
var counter = outer();    // #2
counter.increase();    // === 1
counter.increase();    // === 2
counter.decrease();    // === 1

 var counter2 = outer();
counter.increase();    // === ?   #3


: 이번에도 매우 비슷하다. 하지만 이번에는 이전에 함수를 바로 리턴하던것과는 다르게 #1에서 object 리터럴 {}를 이용해서 함수 2개를 묶어서 리턴하게 했다. #2에서 outer()함수를 호출 할 때에는 #1에서 선언한 object가 리턴이 되어 counter에 들어가게 된다. 이러한 경우에도 똑같이 closure가 생성되고, 이번에는 #1의 object 안에 있는 두개의 함수가 동일한 scope를 보게 되어 같은 작업을 할 수 있게 되었다. 그렇다면 outer() 함수를 한번 더 호출하게 되면 어떻게 될까? 개발자 콘솔에서 직접 실행해보면 아래와 같은 결과가 나온다.




: counter와 counter2는 서로 다른 scope를 생성하여 따로따로 저장하게 된다. 그렇다면 리턴하는 모든 함수들이 같은 값을 사용하도록 static하게 만드는 방법이 있을까?



* 즉시 호출 함수 (immediate function)

: 즉시 호출 함수라 하면 자바스크립트가 익숙하지 않은 사람은 가장 어색하고 당황할 함수 호출 방식이다. 하지만 이미 이전 글들에서도 종종 나왔었다. 맨 위에서 2번째 소스의 예가 바로 즉시 호출 함수의 예이다. 이 즉시 호출 함수를 이용하면 모든 함수들이 공통으로 사용하는 변수를 만들수 있게 된다.

var countFactory = (function () {    // #1
    var staticCount = 0;             // #2
    return function factory () {     // #3
        var localCount = 0;          // #4
        return {                     // #5
            increase: function () {
                return {
                    static: ++staticCount,
                    local: ++localCount
                };
            },
            decrease: function () {
                return {
                    static: --staticCount,
                    local: --localCount
                };
            }
        };
    };
}());
var counter = countFactory(), counter2 = countFactory();
counter.increase();
counter.increase();
counter2.decrease();
counter.increase();


: 그냥 보면 무언가 복잡해진것 같지만, 아주 쉽다. 그냥 위의 예를 하나의 함수로 더 덧씌우면서 closure 하나를 더 생성한 것이다. 즉, #1에서 closure를 생성하는 즉시 호출 함수를 선언하고, #2에서는 static으로 활용할 변수를 선언하고, #3 에서는 즉히 호출 함수에서 리턴 값으로 사용할 함수, 위의 예제에서 사용했던 함수를 리턴하게 되는 것이다. 즉, #1의 리턴 값은 #3이 되어 countFactory 변수에는 #3의 함수가 들어가게 된다. 나머지 #4와 #5는 위의 예제와 똑같고, 단지 local과 static의 차이를 나타내기 위하여 리턴 값에 그 두가지를 묶어서 리턴하도록 했다. 위의 실행 결과는 아래와 같이 나온다.



: 이렇게 즉시 호출 함수를 선언함으로써 closure를 하나 바로 생성하는 방법은 다양한 곳에서 활용될 수 있고, 이것은 기존의 웹 개발과는 확연하게 다른 자바스크립트만의 새로운 개발 방법론으로 자리 잡고 있으므로 반드시 이해하고 넘어가길 바란다. 이제 눈치가 좀 빠른 사람이라면 이렇게 closure를 사용하는 가장 간단한 방법이 바로 함수를 return하면서 사용하는 것이라는 것도 알게 되었을 것이다.



* 라이브러리에서의 활용

: 위와 같이 함수로 한번 둘러싸는 경우 가장 많이 사용하는 것이 바로 라이브러리일 것이다. 라이브러리일 때 뿐만아니라 사용자의 접근을 제한하고, 변수의 조작을 불가능하게 하기 위해서는 필수로 위와 같은 방법을 사용해야한다. 하지만 이것보다도 더 큰 이유는 바로 다른 라이브러리들과 함께 사용되는 경우 서로간 충돌을 없애기 위해 반드시 해야한다. 전역변수를 사용했다가 다른 라이브러리 가져왔는데 그 라이브러리에서 덮어씌워버린다면 이유도 모르고 멀쩡하던 웹 페이지에서 에러가 발생하게 될 것이다.


: 아주 간단한 예를 들어보면, 모두가 많이 사용하는 var xmlhttp를 전역변수로 사용했는데, 잘 못 만든 라이브러리 하나를 가져왔더니 거기서 XMLHttpRequest를 워낙에 빈번하게 사용하다보니 전역변수로 선언해서 사용하고 있는데, 그게 하필 같은 xmlhttp를 전역변수로 사용하기라도 한다면 이전에 선언되고 호출 되었던 부분들이 덮어씌워져 버릴 것이다. 그래서 라이브러리를 만들때에도, 활용할 때에도 위와 같이 자신의 중요한 변수들은 즉시 호출 함수로 감싸서 보호를 해주는 것이 마땅하고 전역변수로부터의 접근은 네임스페이스를 활용함으로써 그 가능성을 최소화 시키는 것이 필요하다. 때로는 전역변수의 사용이 불가피하다고 느낄때가 있겠지만, 거의 모든 상황에서 전역변수의 사용은 회피할 수 있다. 그 이유는 바로 closure로 인해 function을 인자로 넘겨줄 경우 function이 참조하고 있는 scope째로 왔다갔다 하게 되기 때문에, 이쪽의 scope와 다른 곳에서의 scope를 함수를 전달 시킴으로써 공유하게 만드는 것이다.



* Closure가 발생하는 또 다른 경우

: 위처럼 return을 통해서도 closure가 생성되기도 하지만 이외에도 많은 방법으로 생성할 수 있다. 위의 return으로 생성하는 방법은 자바스크립트에 매우 친숙한 사람이라면 다 해봤겠지만, 일반 프로그래머들도 많이 해봤을 경우가 있다. 하지만 아마 본인도 closure가 생성된다는 것 자체도 모르고 활용할 방법도 이해 못하고 있었을 확률이 높다. 첫번째 예는 이 글 맨 위의 첫 예이다.

function setDivClick(index) {
        document.getElementById("divScope" + index).addEventListener("click", 
            function () {  // #1
                alert("You clicked div #" + index);
            }, false);
    }

: 일단 위의 Closure의 특징에서 말한 특징을 찾으면 같은 구조로 함수 안에 함수가 존재하는 것을 발견할 수 있다. 하지만 위의 특징에서와는 다르게 return을 하고 있지는 않다. 아래의 예도 closure가 존재한다는 것을 모르고 자주 사용했을 예이다. 이전에 팁으로 썼던 글 중에서 setInterval에 대한 글 중의 소스이다.

<script>
    var pendingInterval = false;
    function setPending() {
 
        if (!pendingInterval) {
            pendingInterval = setInterval(startPending, 500);
        } else {
            clearInterval(pendingInterval);
            pendingInterval = false;
        }
    }
    function startPending() {
        var div = document.getElementById("pending");
        if (div.innerHTML.length > 12) {
            div.innerHTML = "Pending";
        }
        div.innerHTML += ".";
    }
</script>
<button onclick="javascript:setPending();">Toggle Pending</button>
<div id="pending">Pending</div>


Pending


* 참고

2012/11/26 - [Javascript] Timer : setTimeout, setInterval 함수


: 위의 소스를 보면 setInterval(startPending)을 하고 있다. 이것은 실제적으로 startPending의 scope를 그대로 옮겨와서 closure를 하나 생성하게 된다. 하지만 전역벽수를 이용하고 있기 때문에 만약 사용자가 pendingInterval = true;를 시켜버리면 라이브러리의 동작을 수정하여 어떠한 일이 일어날지 예측하지 못할지도 모른다. 위와 같이 간단한 예에서는 미치는 영향이 적을지 모르지만 고객 정보를 다루거나 응모 이벤트 같은 곳에서 잘못 짜여진 웹페이지를 사용자가 건드리는건 아주 식은죽 먹기이다. 따라서 위의 라이브러리에서의 closure 활용처럼 오류를 방지하도록 수정해보고 덤으로 한번 퍼포먼스를 향상시켜보자.

<button onclick="javascript:setPending2();">Toggle Pending</button>
<div id="pending2">Pending2</div>
<script>
    var setPending2 = (function () {
        var pendingInterval = false, div = document.getElementById("pending2"); // #1

        function startPending() {    // #2
            if (div.innerHTML.length > 13) {
                div.innerHTML = "Pending2";
            }
            div.innerHTML += ".";
        };
        return function () {  // #3
                if (!pendingInterval) {
                    pendingInterval = setInterval(startPending, 500);
                } else {
                    clearInterval(pendingInterval);
                    pendingInterval = false;
                }
            };
    }());
</script>


Pending2


: Closure를 이용하는 아주 간단한 예이고 라이브러리의 기본 형태와 비슷하다. setPending2 변수에는 #3에 선언된 함수가 리턴되면서 설정하게 되고, #1과 #2에 있는 변수와 함수는 closure 내부에서만 접근할 수 있는 private 변수와 함수 같이 된 것이다. 여기서 퍼포먼스를 향상 시켰다는 말은 어디로부터 올 수 있었는지 살펴보면, 바로 매번 startPending 함수가 호출될때마다 div = getElementById()를 하던 것을 한번만 하도록 closure에 변수로 저장해둔 것이다. 이것은 전역변수가 아니라서 전역변수를 싫어하는 개발자들의 마음을 아프게하지도 않는다. 이전까지 전역변수가 난무하던 자바스크립트의 개발 방법이 closure에 대한 이해가 늘어나면서 이러한 식으로 encapsulate하고 자기의 변수를 보호하는 방식의 개발 방법론으로 개선되고 있는 것이다. 여기서 중요한 것은 closure를 이용했기 때문에 전역변수를 이용하지 않고도 위의 setPending2 함수는 인자로 보내든, 다른 라이브러리에서 사용하든 어디서든 똑같은 동작을 하게 되는 것이다. 하지만 위의 구현 방법이 만능은 아니다. 아래와 같은 단점들을 대표적으로 꼽을 수 있다.

  • 즉시 호출 함수는 소스가 로드되면 바로 실행을 하게 되므로 소스의 로딩시간이 길어진다.
  • 소스가 바로 실행되므로 html 소스보다 아래에 있어야한다.

: 이러한 단점들은 물론 극복 가능하거나 다른 방법으로 고민을 해볼 수 있다. 일단 두 번째 단점 때문에  대표적인 해결 방법이 즉시 호출 함수를 window.onload 이벤트에 넣는 방법이 있다. 이렇게 되면 html 소스 등 웹페이지의 로딩이 다 끝나고난 뒤에 함수를 호출하게 되므로 소스가 어디에 있든 상관없게 된다. 이렇게 즉시 호출 함수가 많아짐에 따라 window.onload의 활용은 이전보다 확연하게 많아진 것이다. 첫 번째 단점은 여러 모로 조금 고민을 해봐야한다. 다음 중에서 가장 중요한 것을 고민해볼 수 있을 것이다.

  • 페이지의 첫 로딩시간은 조금 느리지만 사용자의 지속적인 인터렉션 반응 속도의 단축
  • 사용자의 첫 클릭에서의 반응 속도는 느리지만 지속적인 반응 속도 단축
  • 조금 느리지만 사용자의 꾸준하게 동일한 반응 속도
: 위의 3가지 중에서 첫번째는 위의 예처럼 즉시 호출 함수를 이용하는 방법이다. 처음에 로딩하면서 div를 로딩해두는 것이다. 그 다음 3번째는 맨 처음 구현했던 방법이다. 매번 div를 DOM 트리에서 가져오기 때문에 지속적으로 조금은 느린 사용자 반응속도가 일어나게 되는 것이다. 그럼 2번째는 어떠한 경우인지 살펴보자.


* 덧

자바스크립트의 퍼포먼스를 저해하는 가장 큰 요소는 'DOM을 탐색하여 접근'하는 것이다. getElementById이든 jquery의 $이든 상관없다. 따라서 위처럼 한번 접근하고 나서 다시 접근을 자주 할 것 같을 때 변수에 저장해두고 접근을 하는 것이 퍼포먼스를 향상 시킬 수 있는 큰 방법이다.





* 자기를 덮어쓰는 함수 (self-defining function)

: 이 방법은 사용자들에게나 라이브러리를 이용하는 개발자들에게는 전혀 나타나지 않고 다른 점을 못느끼겠지만 이 자체를 개발하는 개발자라면 매우 뿌듯함(?)을 느낄 수 있는, 만약에 이전에 이러한 설계를 한번도 보지 못했다면 아주 기가막힌 설계 디자인이다. 간단하게 요약하자면 바로 초기화를 호출 단계에서 하고 자기 자신을 그 초기화된 정보들을 포함하는 closure가 있는 함수로 덮어씌우는 것이다. 위의 예를 다시 한번 사용해보자.


<button onclick="javascript:setPending3();">Toggle Pending</button>
<div id="pending3">Pending3</div>
<script>
    var setPending3 = function () {
        var pendingInterval = false, div = document.getElementById("pending3"); // #1

        function startPending() {    // #2
            if (div.innerHTML.length > 13) {
                div.innerHTML = "Pending";
            }
            div.innerHTML += ".";
        };
        setPending3 = function () {  // #3
            if (!pendingInterval) {
                pendingInterval = setInterval(startPending, 500);
            } else {
                clearInterval(pendingInterval);
                pendingInterval = false;
            }
        };
        setPending3();
    };
</script>

Pending3

 


: 이번은 2번째 예와 비슷하지만 다른 점이 있다면 setPending3는 처음에 호출되었던 함수를 그 안에서 다시 덮어씌워서 다른 함수 #3으로 만들어버린다는 것이다. #3에서는 맨 처음의 setPending3 함수의 #1과 #2에서 가지고 있었던 변수와 함수들에 대하여 접근할 수 있는 closure가 생성되어 유지된다. 다른 점이 있다면 2번째는 소스가 로딩됨과 동시에 호출이 되어 초기화 작업이 이루어졌다면, 이번에는 함수 호출이 일어날 때, 즉 사용자가 처음으로 버튼을 클릭해서 처음으로 호출이 될 때 초기화를 하게 된다. 이게 뭐가 좋은지 잘 모를수도 있지만, 매번 호출될 때마다 초기화가 되었는지 if-else를 해볼 필요없이 그냥 함수 자체를 다시 선언해주는 것이라고 생각하면 이후에 호출될 때마다 성능상으로 충분한 메리트가 있다고 생각할 수 있다. 이렇게 사용자가 처음에 클릭했을 때 초기화하는 것은 아주 사소한 차이지만 프로그램이 커졌을 경우나 DOM을 자바스크립트로 대량으로 다루게 될 때에는 UX가 초기화가 로딩 때 일어나느냐 처음으로 눌렀을 때냐 등의 차이에 따라 꽤나 크게 다가올지도 모른다. 즉, 서로 다른 기능의 특징에 따라 서로 다른 초기화 방법을 사용하면 된다. 이것은 각자 생각하는 가치관에 따라 다르기 때문에 대충 한번 간단하게 어떻게 구현하느냐에 따른 기준을 적어보자면 아래와 같다.

  • 사용자가 페이지에 접속하자마자 자주 사용하는 기능, 이 페이지에 들어와서 반드시 한번은 사용하게 되는 기능이라면 로드하면서 초기화
  • 사용자가 페이지에 들어와서 한참 후에 사용하겠지만 한번 쓰고나서 자주(혹은 이따금씩) 이용하게 되는 기능은 처음 호출 때 초기화
  • 사용자가 기능을 건드리지 않고 나갈 가능성이 크고 자주 이용하지도 않는 기능은 초기화 단계 없이 그냥 그때그때 사용

: 이것이 자바스크립트 초기화에 대한 기본 이해가 될 것이다. 그렇다면 이번에는 closure를 응용하는 한가지 예를 살펴보자.



* 오버로딩

: 객체 지향 개발자라면 아주 반가운 단어일 것이다. 자바스크립트에서는 유동적으로 인자의 수를 받아들이기 때문에사실 오버로딩을 지원하지 않는다. 하지만 arguments와 closure를 이용한다면 이러한 오버로딩 개념도 나름 구현할 수 있게 된다. 간단하게 작성해보면 아래와 같다.

function overload(object, functionName, fn, fnMap) {
    var previousFunction = object[functionName];
    object[functionName] = function () {
        if (fn.length === arguments.length) {
            if (fnMap && fnMap.length === arguments.length) {
                for (var i = 0; i < arguments.length; i++) {
                    if (fnMap[i] === typeof arguments[i]) {
                            return previousFunction.apply(this, arguments);
                    }
                }
                return fn.apply(this, arguments);
            } 
            return previousFunction.apply(this, arguments);
        } else if (typeof previousFunction === "function") {
            return previousFunction.apply(this, arguments);
        };
    };
}

 

: 복잡한듯 하지만 함수를 호출하게 되면 객체에 저장되어있는 함수를 previousFunction으로 closure에 저장해두고, 인자의 갯수와 인자의 형을 비교해서 모든게 일치하다면 인자로 넘어왔던 fn을 호출하고, 아니라면 이전에 설정되었던 다른 함수 previousFunction을 호출하게 되는 것이다. 간단한 활용 예를 들면 아래와 같다.

var MyFile = {};
overload(MyFile.prototype, init, function () {
    console.log("init as empty file");
});
overload(MyFile.prototype, init, function (fileName) {
    console.log("init with file name");
}, ['string']);
overload(MyFile.prototype, init, function (file) {
    console.log("init with file object");
}, ['object']);

var file = new MyFile();
file.init();
file.init("myfile.txt");
file.init(fileObject);


: 이러한 활용 예를 들 수 있겠다. 위의 예는 아주 간단하게 오버로딩을 구현한 기법이고, 에러 체크라던가 기본 호출 함수 설정 등 구미에 맞게 바꿔서 구현하면 될 것이다.


 

* closure 단점

: 이렇게 편리한 closure라도 만능은 아니다. 엄연히 단점이 있기 때문에 항상 사용하기 보다는 정말로 필요할 때, 구현에 있어서 급진적으로 개발이 편해질 때 사용하면 좋을 것이다. 이러한 closure의 단점은 크게 2가지이라고 볼 수 있고 부수적인 단점이 한가지 더 있다. 일단 큰 단점 2개는 다음과 같다.

  • 메모리를 소모한다.
  • Scope 생성에 따른 퍼포먼스 손해가 있다.

: 이들 2가지는 어떻게 극복할수 없는 단점들이다. Closure를 정말로 필요한 곳에 요긴하게 사용하는 수 밖에 없다. 특히, 메모리의 소모는 리턴하거나 timer, 콜백 등으로 등록했던 함수들이 메모리에 계속 남아있게 되면 해당하는 closure도 같이 메모리에 계속 남아있게 되는 것이기 때문에, 지속적으로 루프를 돌면서 closure 생성하는 것은 지양해야할 설계가 될지도 모른다. 최신 버전에서는 해결되었지만, 구 버전의 IE 같은 경우에는 DOM의 콜백함수로 등록을 하고 콜백함수의 해제 없이 바로 DOM을 삭제해버리면 메모리 누수가 생기는 단점도 있었던 점만 봐도 closure의 메모리 누수와 누적에 대한 고민을 해야한다는 것을 깨달을 수 있다. Closure는 또한 하나의 새로운 Scope를 생성하여 내부의 함수에 링크를 시키기 때문에 이에 따른 퍼포먼스 손해도 감수해야한다. 잦은 함수 호출이 퍼포먼스상 안 좋듯 만약 굳이 함수나 closure를 사용하지 않아도 되는 간단한 일이라면 굳이 함수로 분류를 하지 않아도 될 것이다.


: 그렇다면 위의 핵심적인 단점들 이외의 부수적인 단점은 무엇일까? 다른 언어의 개발 경험이 많았던 사람이라면 조금은 느꼈을지도 모른다. 바로 이해하기가 어렵다는 것이다. Closure는 개발자 본인이 사용할 때에는 나비처럼 날고 벌처럼 쏘는 핵심 기능으로 자라나지만 다른 사람들이 보면 이것이 무엇인지, 어디서 closure가 생성되었고 거기에는 어떠한 정보가 있는지 불분명하게 되는 경우가 많기 때문에 협업을 하게 될 때에는 명확한 주석과 문서화가 필요로 있어야할 것이다. 그리고 무엇보다도 다른 언어 개발자들은 closure가 돌아가는 방식을 이해하지 못하는 경우가 많기 때문에 그들에게 설명을 해줘야할 시간을 투자해야 한다는 점과 이 개념을 같은 웹개발자라도 제대로 이해하고 있지 않는 사람이 정말 많다는 것에 놀라 마음의 충격을 받는다는 점이다.



* 단점에도 불구하고..

: 이러한 단점들은 정말 어떻게 다른 방법으로 극복이 불가능한 단점들이다. 하지만 자바스크립트에서 closure를 빼면 그것은 진정한 자바스크립트가 아니다. 정말로 단순한 '스크립트 언어'에 머물던 5년전 closure를 배제한 개발 방식이야말로 자바스크립트에 잠재되어있는 무한한 가능성을 없애는, 정말로 너무나 놀라운 언어를 그냥 '스트립트언어'로, 그냥 보조적인 언어로 만들어 버리는 것이다. 위에서 단점을 쓴 것은 단점이 많아서라던가 치명적이어서가 아니라 장점이 훨씬 더 많지만 적어도 어떠한 단점들이 있는지 알고 사용해야 더욱더 잘 사용할 수 있기 때문이다. 이 closure를 마음껏 쓰다가 다른 언어를 사용하게 되면 closure가 없다는 것이 엄청 아쉬울 때가 많아질 만큼 closure는 자바스크립트의 핵심이자 특징이라고 볼 수 있다.



* 정리

- Closure는 function 안에 function이 있을 때 생성된다.

- Closure는 함수가 정의된 scope 이외의 곳에서 사용될 때 private 저장소처럼 활용 가능하다. 이러한 경우가 발생하는 대표적인 경우는 아래와 같다.

    • 내부의 function을 리턴하여 다른 곳에서 사용 (바로 호출하던지 인자로 넘겨주는 등)
    • setTimeout, setInterval 등과 같이 비동기적으로 호출 되는 경우
    • 이벤트 콜백 함수로 활용 되는 경우 (addEventListener 라던가, xmlhttprequest.onreadystatechange 등)

- Closure를 이용해서 초기화할 때 각 상황에서의 퍼포먼스를 고려하면 좋다.

- Closure의 단점은 메모리와 퍼포먼스에 있다.

- 하지만 Closure는 자바스크립트의 핵심이다!



: 다음에는 위에 잠깐 언급되어있는 prototype에 대하여 한번 속깊게 들어가보자.


[속깊은 자바스크립트 강좌] Closure의 이해 / 오버로딩 구현하기 끝.


- 다음 편

2013/01/30 - [속깊은 자바스크립트 강좌] Closure 쉽게 이해하기/실용 예제 소스

2013/02/13 - [속깊은 자바스크립트 강좌] 쉬어가기: 웹 개발 방법론의 변화/자바스크립트의 재발견

2013/02/22 - [속깊은 자바스크립트 강좌] 객체지향의 기본: prototype

2013/10/04 - [속깊은 자바스크립트 강좌] 상속, new와 Object.create의 차이

2013/10/29 - [속깊은 자바스크립트 강좌] 글로벌(전역) 변수와 window 객체

2013/11/06 - [속깊은 자바스크립트 강좌] 변수 선언 방법에 대하여

2016/11/13 - [속깊은 자바스크립트 강좌] 마무리(는 책으로!)


저작자 표시 비영리 동일 조건 변경 허락
신고

이 글을 공유하세요.

  • 뎁퍼니 2013.01.21 17:58 신고  댓글주소  수정/삭제  댓글쓰기

    자바 스트립트 강좌 유익하게 잘 보고 있습니다
    제가 초보자라 갈수록 어려워지네요 흑흑
    이번 글은 여러번 반복해서 읽어야 되겠어요
    좋은글 감사합니다^^

    • Unikys 2013.01.21 18:10 신고  댓글주소  수정/삭제

      그쵸~ 자바스크립트에서는 closure가 제일 고비이긴합니다만 이것만 이해한다면 나머지는 아주아주 쉽습니다! 마치 C로 보자면 포인터와 비슷한 위치이지요~ 제대로 이해하면 진정한 자바스크립트 개발자가 되는 것이지요! 그럼 prototype은 다음에 하고 먼저 정말 쉽게 이해할 수 있게 closure를 한번 정리해보겠습니다^^ closure를 이해하고 한 번 직접 써보고 다시 읽어보신다면 무릎을 탁~ 치게 되실거라 생각합니다!

  • 뎁퍼니 2013.01.29 09:16 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 unikys님
    여러번 읽다보니까 감이 좀 오는거 같네요
    네번쩨 예제(Closure의 특징)에 보면 return count; --> return ++count; 아닌가해서요
    한가지 양해를 구하고자 하는데 제가 이번에 자바스크립트 자료를 정리해서 발표를 하는데
    여기에 나오는 샘플 예제를 자료에 넣을려고 하는데 가능할런지요 ^^

    • Unikys 2013.01.30 11:00 신고  댓글주소  수정/삭제

      아~ 오타가 맞네요! 예리하십니다 ㅎㅎ 수정하겠습니다!
      소스 예제는 자료에 넣으셔도 됩니다~ 자바스크립트에 대한 인식을 조금이라도 바꾸는데 도와주세요!^^

  • 류나 2013.12.17 20:01 신고  댓글주소  수정/삭제  댓글쓰기

    우와....클로저도 그렇고 내용이 너무 어렵네요...ㅠㅠ.
    앞으로 엄청 노력해야겠어요

  • 김민수 2014.05.02 15:13 신고  댓글주소  수정/삭제  댓글쓰기

    글 잘봤습니다. 궁금한게 있어서 글올립니다. 클로저를 성립 하기 위해서는 인스턴스화를 반드시 해야 하는지요. 예를 들어 예제에,

    function outer () {
    var count = 0;
    var inner = function () {
    return ++count;
    };
    return inner;
    }
    var increase = outer(); // #4

    increase();
    increase();

    에서 #4 부분이 없으면 같은 값이 나오더군요. 당연한 개념으로 생각되지만 이 부분에 대해서 설명한 어떠한 블로그도 없기에 여쭈어 봅니다. 클로저라는 개념이 워낙 독특하다보니 인스턴스화를 하지 않아도 되지 않을까라는 생각에 ...

    • Unikys 2014.05.03 20:15 신고  댓글주소  수정/삭제

      네, 클로져를 만들기 위해서는 밖에 클로져를 만드는 함수와 내부의 함수 모두 인스턴스화할 필요는 없습니다. 외부의 함수를 인스턴스화할 필요가 없는 것은 즉시호출 함수(immediate function)로 클로져를 만들 때이고요, 내부의 함수를 인스턴스화할 필요가 없는 경우의 대표적인 예가 addEventListener 등과 같은 콜백함수를 설정할 때이지요. 하지만 이러한 경우 엄격히 따져보면 결국 function () {...}의 익명함수나, (function(){})() 등과 같이 즉시호출 함수(immediate function)나 function literal로서, 내부적으로는 Function 오브젝트로 인스턴스화가 되는 과정을 거치기는 합니다. 단지 반드시 변수에 할당을 안해도 되는 것 뿐이지요. 변수에 할당하는 이유는 클로져를 이용하는 경우 어디선가 클로져의 외부에서 호출이 가능해야 하는데 외부에서 호출하는 것은 콜백기능이 아니면 이렇게 변수에 할당하는 방법 말고는 없기 때문이지요. 재미있는 질문 올려주셔서 감사합니다^^

  • 필정 2015.02.10 00:47 신고  댓글주소  수정/삭제  댓글쓰기

    셀프 디펜딩 패턴 대박이네요!!
    var exam = function() {
    ...
    exam = function(){};
    };
    처럼 아예 비워주는건 사용해 본적이있는데 애초에 초기화를 시키고 호출하는 방법은 생각도 못해봤네요. 항상 변수를 꼭 전역으로만 줘야해서 안타까웠는데 정말감사합니다. 클로저가 정말 대단하네요 ㅠㅠ
    아직 취준생이지만 공부하면서 많이 정보얻어갑니다. 제블로그에 비슷하게 포스팅해도 될까요?? 물론 출처남기겠습니다!!
    진짜 감사합니다.

    • Unikys 2016.05.01 03:35 신고  댓글주소  수정/삭제

      네, 오래전의 댓글이라 이미 옮기셨을거라 생각하는데 출처만 남기시면 됩니다. 즐거운 자바스크립트 공부 되시길 바랍니다.

  • 2016.01.12 18:03  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  • Engineer135 2016.12.22 11:17 신고  댓글주소  수정/삭제  댓글쓰기

    클로져... 어렵네요 ㄷㄷ

  • 이재준 2017.05.15 21:01 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요~
    책을 보다가 이해가 안되는 부분이 있어서 검색하다보니 블로그가 있으시네요~!
    staticCount 이라는 클로저와 IIFE로 스코프를 하나 더 만들어서 공용 변수를 사용하는 클로저 방법을 보고 궁금해서 여쭤봅니다.

    var countFactory = (function () { // #1
    var staticCount = 0; // #2
    return function factory () { // #3
    var localCount = 0; // #4
    return { // #5
    ...
    }()

    #1에서 IIFE로 스코프, #2 staticCount 변수 추가한 후 #3을 반환하면
    staticCount은 공용, localCount 로컬로 사용이 가능하다고 하였는데,
    이 부분이 확실히 이해가 되질 않네요.

    그 위에 있는 예제를 보면

    function outer () {
    var count = 0;
    return { // #1
    increase: function () {
    return ++count;
    },
    ...
    }

    위 말씀대로라면 outer에서도 스코프가 생성되고 count 아래에 있는 return 객체가 반환되니
    count는 공용으로 사용할 수 있다는 생각이 듭니다.

    두개의 차이점을 자세하게 설명해주실수 있나요?







    • Unikys 2017.05.16 06:56 신고  댓글주소  수정/삭제

      두개의 다른 점이 있다면 outer()를 통해서 생성되는 함수(객체의 함수)들 간에 공유되는 변수가 없다는 점입니다. 예를 들면,

      var outer1 = outer();
      var outer2 = outer();
      여기서 outer1과 outer2 변수간에 공유되고 있는 변수는 없고, outer 함수 안의 count 변수는 각 outer1과 outer2 사이에서 독립적이지만,
      var count1 = countFactory();
      var count2 = countFactory();
      를 했을 때에는 count1과 count2 사이에 staticCount라는 변수는 동일하게 참조되어 공용 변수로서 사용할 수 있는 것입니다. 그리고 localCount 변수는 count1과 count2가 서로 독립적으로 가지게 됩니다.

  • 이재준 2017.05.16 20:57 신고  댓글주소  수정/삭제  댓글쓰기

    답변 감사합니다.
    죄송하지만 staticCount가 공용으로 되는 원리에 대해서 이해가 가질 않습니다.

    outer()에서 counter가 return 되지 않고, 그 아래에 있는 함수가 return이 되고
    countFactory()에서도 staticCount아래에 있는 함수만 return 되는 건 같은데
    왜 즉시 호출 함수를 사용한다고 해서 아래에 있는 변수가 공용이 되는건지
    스코프 생성과 관련이 있는것 같은데 이해가 되질 않네요..ㅠㅠ

    죄송하지만 답변 부탁드립니다.

    • Unikys 2017.05.17 03:40 신고  댓글주소  수정/삭제

      함수명을 조금 바꿔서 비교해보시면 조금 더 쉬우리라 생각합니다. 아래의 예제에서 중간에 return function factory ()를 return function outer ()와 같이 바꾼 뒤 위의 소스와 비교하시면 두개의 구조가 어떻게 다른지 조금 더 쉽게 이해 되시리라 생각합니다.
      즉시 호출 함수는 이러한 공용 변수를 만드는 가장 일반적인 방법입니다. staticCount 변수는 즉시 호출 함수 스코프에 있고, 이 즉시호출함수는 function factory()를 정의하여 외부로 리턴하고 있습니다. 이 function factory() 함수는 같은 즉시호출함수 스코프에 있는 staticCount 변수에 참조가 가능하게 되는 것입니다. 그리고 이 외부로 리턴된 factory()를 호출하면 위의 outer()위 동일하게 객체를 리턴하지만, factory() 함수는 즉시호출함수에서 정의한 staticCount 변수가 계속 참조 가능한 것이 다릅니다.
      아마 다음 편을 한번 읽어보시면 더 쉽게 이해하실 수 있을 겁니다.

질문이나 의견을 댓글로 달아 주세요