개요

 

자바스크립트는 클로저(Closure)라는 매우 중요한 개념이 있다. 자바스크립트로 프로그램을 구현하다보면 은연 중에 이러한 개념들을 사용하곤 했는데 확실하게 알고 있지는 않았다. 그래서 오늘 강좌에서는 이 부분에 대해 확실히 알고 넘어가볼까 한다.


클로저란 특정 함수 내에 정의된 지역 변수(Local Variable)를 외부에서도 참조(By Reference) 할 수 있는 함수를 말한다.

무작정 말로만 들으면 이해하기 어려울 수 있으니 먼저 아래 예제를 보도록 하자. (예제 링크)

1
2
3
4
5
7
8
9
function closureTest(a){
     return function(b){
          return a + b;
     }
}

var test = closureTest(1);
alert(test(2));


 

위의 예제를 실행하면 "3"이라는 메시지가 출력된다. 참고로 closureTest 함수에서 a 변수는 지역 변수이다.

1
2
3
4
5
6
8
9
10
function closureTest(a){
     var a2 = a;
     return function(b){
          return a2 + b;
     }
}

var test = closureTest(1);
alert(test(2));

위의 두 예제의 실행 결과 값은 똑같이 "3"이다. return되는 함수가 클로저인데 동작하는 함수를 그림으로 표현하면 다음과 같다.


closureTest()에 "1"을 인수로 설정하여 실행하면 함수 내부는 다음 그림과 같이 동작한다.

closureTest()를 생성하면서 인수로 "1"을 지정하면 중첩 함수인 클로저는 그 값을 참조한다.
따라서 최종적인 클로저는 '인수로 받은 b에 기존에 포인터로 가지고 있던 "1"을 더해서 반환하는 함수'이다.

이제 test(2)를 실행하면 다음과 같이 동작한다.

따라서 test(2)는 최종 결과로 "3"을 출력하며 이 예제에서 중요한 것은 참조이다.  
즉, 값 자체를 복사하는 것이 아니라 값을 참조하고 있으므로 참조하는 값이 바뀌면 참조하고 있던 값도 함께 바뀌게 된다.




심화

 

지금까지의 설명을 들어도 클로저 개념이 어렵게 느껴질 수도 있는데, 이해를 돕기 위해 비유를 해서 설명을 하자면 클로저는 C언어에서 "&" 키워드를 사용하여 정의하는 참조 변수라고 생각하면 된다. 단지 표현 방법의 차이가 있을 뿐, 실제 개념은 비슷하다고 보면 된다.


그럼, 아래 예제를 보도록 하자. (예제 링크)

1
2
3
4
5
6
7
8
9
10
11
function plus() {
     var i = 1;
     return function(){
          alert(i++);
     };
}

var p = plus();
$("#but").click(function() {
     p();
});


위의 예제를 보면서 문득 떠오른 것이 있다. 그것은 바로 OOP의 클래스 개념이다.


예전에 연재 했던 JS Tip & Tech (1) - OOP 기초 편을 보면 자바스크립트에서 함수는 OOP에서 클래스와 비슷하다고 설명한 적이 있다. 함수가 클래스와 비슷하다면, plus 함수 안에 선언된 "i"는 단순히 함수 안에서 사용되는 지역 변수가 아닌 클래스 맴버가 되기도 한다.


즉, 지역 변수 "i"의 호출 방식은 "By Value"가 아닌 "By Reference"가 될 수도 있다는 것을 의미한다. 우리가 이때까지 클로저 개념에 대해 이해하기가 어려웠던 이유는 자바스크립트에서 클래스라는 존재는 단순한 함수 정의와 다르지 않기 때문이다.


1
2
4
5
6
7
8
9
10
11
12
var plus = new function() {
     var i = 1;

     this.add = function() {
          alert(i++);
     };
}

$("#but").click(function() {
     plus.add();
});


위의 예제는 "new" 연산자를 사용하여 plus 함수를 객체화하였기 때문에 지역 변수 "i" 값을 plus 함수(클래스)의 맴버로써 참조할 수 있는 것이다. 


그럼, 추가적으로 클로저에 대한 이해를 돕기 위해 다음 예제를 보도록 하자. (예제 링크)

1
2
4
5
6
7
8
10
11
12
14
15
16
18
19
20
21
22
24
25
function buildList(list) {
     var result = [];

     for(var i = 0; i < list.length; i++) {
          var item = 'item' + list[i];
          result.push( function() {alert(item + ' ' + list[i])} );
     }

     return result;
}

function testList() {
     var fnlist = buildList([1,2,3]);

     for(var j = 0; j < fnlist.length; j++) {
          fnlist[j]();
     }
}

testList();


예제의 출력 값이 "item1 1", "item2 2", "item3 3"으로 출력될 것으로 예상하였다면 클로저를 아직 이해하지 못한 것이다.

7라인에서 item과 list[i] 값은 result 배열에 push 될 때, 값 자체가 복사되서 들어가는 것이 아니라 참조되어 들어가기 때문에 "i"의 최종 값인 "3"으로 저장된다. 그렇기 때문에 출력 값은 "item3 undefined"가 된다.

 

그럼, 우리가 의도하고 있는 값을 출력하기 위한 아래 예제를 보자. (예제 링크)

1
2
4
5
6
7
8
9
10
12
13
14
16
17
18
20
21
22
23
24
26
27
function buildList(list) {
     var result = [];

     for (var i = 0; i < list.length; i++) {
          (function(ii) {
               var item = 'item' + list[ii];
               result.push( function() {alert(item + ' ' + list[ii])} );
          })(i);
     }

     return result;
}

function testList() {
     var fnlist = buildList([1,2,3]);

     for (var j = 0; j < fnlist.length; j++) {
          fnlist[j]();
     }
}

testList();


익명 함수를 사용하여 간단하게 해결 할 수 있다. 그럼, 6라인과 9라인을 보도록 하자.

"i"의 값을 익명 함수의 매개변수로 넘겨 "ii" 인자 변수로 받음으로써 "i" 변수의 참조에서 벗어날 수 있게 되었다.

 

[ 출처 ] http://blog.naver.com/seogi1004/110120770472

 

추가 참고 : 클로저 개념 1

                클로저 개념 2

                MDN : https://developer.mozilla.org/ko/docs/JavaScript/Guide/Closures


+ Recent posts