자바스크립트는 매우 뛰어난 함수형 언어이다. 자바스크립트는 클로저를 활용하는 방법이 존재하는데 이를 사용하기 위해서는 심도있는 이해가 필수적이라 할 수 있을 것이다. 본 페이지에서는 자바스크립트의 클로저가 무엇인지 알아보고 클로저(closure)를 활용하여 카운트 다운 코드를 만들어 보고자 한다. 그럼 클로저가 무엇인지 우선 알아보도록 하자.


! 클로저(Closure)란?
클로저는 함수내에서 선언된 변수는 해당하는 스콥(Scope)내에서 생성된 객체(가비지 컬렉션)안에 저장되는데 이를 클로저라고 부른다.

클로저를 한줄로 표현하자면 위와 같지만 이는 쉽게 이해하기 어렵다. 중요한 부분은 클로저는 해당하는 스콥, 즉 동일 영역에서만 존재한다는 점이며 이는 다음과 같은 특징을 갖는다는 점이다.

i. 클로저는 동일영역(해당하는 Scope)을 벗어나 외부에서 접근할 수 있게한다.
; 이를 활용하여 특정 스콥에만 존재하는 변수를 사용, 클로저를 활용한 함수를 만들 수 있게한다. 또한 캡슐화 역시 이와 관련이 있다.

ii. 영역이 다른... 즉, 스콥이 다른 변수간의 충돌을 막을 수 있다.
; 이는 플러그인등의 컴포넌트를 만들 경우 유용하게 활용할 수 있다.



!! 클로저를 활용한 예제보기
아래에서는 클로저를 활용하여 카운트 다운이 가능한 변수를 만들어보고자 한다. 아래 예제를 통해 클로저가 무엇인지 더 정확히 알아보고 어떤 방법으로 활용할 수 있는지 생각해보자.

먼저 카운트다운에 활용할 함수는 counter() 사용자 함수이다. 코드는 아래와 같다.

var counter = function(num) {
   var x = num;
   minus = function() {
      return --x;
   }
   return x;
}

위 함수는 클로저를 활용하여 할당된 매개변수를 x 변수에 저장하고 계속 -1씩 줄여가는 코드이다. 해당 코드를 실행하면 어떻게 될까?

counter(10);

10 // 실행결과 : 9를 출력

이처럼 10을 입력할 경우 10이 입력되었음을 확인할 수 있다. 만약 아래와 같이 minus() 함수를 실행하면?

minus();

9 // 실행결과 : 9을 출력

minus() 함수는 전역함수로 전역 스코프에서 접근이 가능하다. 이를 계속 실행하면 아래와 같을 것이다.

minus();
8

minus();
7

...
..
.

minus();
0

여기서 중요한 부분은 함수 내부에 선언된 지역변수 x이다. 우리는 x에 접근 할 수 없는데 이는 x  변수가 해당 영역에서만 동작하기 때문이다. 하지만 전역변수인 minus() 함수는 x 변수와 스코프 체인선상에 있기 때문에 제한적인 접근이 가능하다. 위처럼 값을 하나씩 빼는 것이 가능하다.


!!! 생각해볼 문제
클로저(Closure)를 사용하는 이유는 해당 스콥인 내부변수를 잘 활용하기 위함이다. 만약 위의 counter() 함수를 사용할 경우 두개의 동일한 카운터를 만든다면 문제가 생길 수 있다... 즉 위 함수는 클로저를 활용했지만 효과적으로 활용하지 못했다... 예를들어 카운터가 두 개를 만들어 적용할 경우 위 코드를 사용할 경우 전역변수 minus()의 동일 참조가 일어나기 때문이다.

결국 전역변수를 내부함수(inner function)으로 그대로 사용하되 x를 반환하도록 변경하면 가능할 것이다. 이를 위해서 아래와 같이 코드를 작성한다.


var counter = function(num) {
   var x = num;
   var minus = function() {
      return --x;
   }
   return minus;
}

여기서 차이점은 minus 전역 scope을 내부 함수로 변경하여 내부 scope로 변경하였고 동일 영역에서 return 하도록 하였다. 이를 다음과 같이 실행 할 경우 각각의 카운터가 실행된다.

counter1 = counter(10);
counter2 = counter(100);

counter1();
9 // 10에서 1을 뺀 9를 반환함

counter2();
99 // 100에서 1을 뺀 99를 반환함


# 객체의 인스턴스를 사용한 클로저 사용
또 다른 방법으로 객체를 사용해 각각의 인스턴스를 만들면 어떨까? 객체를 사용하여 minus()를 전역함수가 아닌 객체의 메소드(method)로 사용하면 이런 문제는 말끔히 해소될 수 있을 것이다. 다수의 데이터에 동일한 함수를 사용할 경우 함수보다는 객체의 메소드를 사용하는 것이 더 효과적일 수 있을 것이다.

위와 같이 만들기 위해서는 counter()를 생성자 함수로 만들고 이를 통해 각각의 인스턴스를 생성해야 한다.

counterA
counterB

여기서는 각각의 인스턴스를 위와 같이 정하였다. 그렇다면 다시 한번 함수를 만들어보자. 우선 생성자 함수를 만들어야하는데 일반적으로 대문자를 사용하므로 아래와 같이 만들 수 있을 것이다.

// 생성자함수 Counter() 만들기
Counter = function() {
   var x;
   this.setNum = function(num) {
      x = num;
   }
   this.minus = function() {
      return --x;
   }
}

위의 생성자 함수를 사용하여 아래와 같이 new 키워드를 사용해 두개의 인스턴스를 생성 완료하였다.

counterA = new Counter();
counterB = new Counter();

인스턴스를 사용하고 변수 x를 참조하는 것 모두 맨 위의 함수와 동일하다. 동일하게 클로저를 사용하였고 각각의 변수 x는 접근할 수 없는 것 역시 동일하다. 그럼 테스트를 통하여 100, 10을 가진 카운터를 생성해보자.

counterA = new counter();
counterA.setNum(100); // CounterA의 시작값을 100으로 설정

counterA.minus(); // 100에서부터 1씩 계속적으로 줄여감

99 // 100에서 1을 뺀 값을 출력

counterB 역시 동일하다.

counterB = new counter();
counterB.setNum(10); // CounterB의 시작 값으로 10을 설정함

counterB.minus(); // 10에서 1씩 계속 줄여감

9 // 10에서 1을 뺀 값을 출력

여기까지 클로저를 사용하는 이유와 클로저가 어떻게 작동하는지 알아보았다. 클로저를 사용하면 복잡하고 긴 코드를 간결하게 줄일 수 있도록 도와주기 때문에 중급 이상의 개발자라면 클로저를 잘 활용할 수 있어야 하겠다.