javascript 클로저(closure)의 활용(2편)

클로저(closure)의 활용

본 글은 독자가 자바스크립트 클로저의 개념에 대한 이해를 전제하에 작성된 글이다.

자바스크립트의 클로저의 개념에 대해서는 공부를 했지만, 막상 이 클로저라는 것을 활용해 보려니 도통 감이 오질 않는다. 클로저를 활용한다는 말인 즉, 클로저를 통해서 어떤 프로그래밍 적 효과를 발생시키고 그 효과를 활용 한다는 말일 것이다.

그렇다면 간단한 예제를 통해 그 효과와 그 효과를 활용하는 방법을 알아보자. (클로저는 성능적인 면과 자원적인 면에서 약간 손해를 볼수 있으므로 무차별적으로 사용해서는 안된다고 한다.)

활용1 - 함수 캡슐화

설명 할 클로저는 함수형 프로그래밍 기법이지만, 캡슐화를 구현할 수 있다. 캡슐화란 객체지향 프로그래밍의 하나의 개념 중 하나이다. 객체지향 프로그래밍의 캡슐화란 다음과 같다.

캡슐화란?

객체의 속성(data fileds)과 행위(메소드, methods)를 하나도 묶는다. 실제 구현 내용 일부를 외부에 감추어 은닉한다.

하지만 자바스크립트에서 기본적인 객체선언은 캡슐화의 두번째 측면인 실제 구현내용을 외부에 감추어 은닉할수가 없다. 간단한 객체 리터럴 예제로 확인해보자.

1
2
3
4
5
var countObj1 = {
n : 0,
count : function(){ return this.n++;},
reset : function(){ this.n = 0}
}

countObj1객체의 의도는 메소드 count를 이용해서 n을 1씩 증가시키는 것이다. 그리고 reset 메소드를 이용하여 n을 0로 초기화 시킨다.

그리고 위처럼 사용 할수 있다. 하지만 객체의 내부 프로퍼티인 n을 다른 방식으로 접근하여 호출 또는 재정의 할 수 있다.

위처럼 내부 객체의 내부 프로퍼티에 직접 접근하여 재 할당 될 수 있다. 만약 악의적인 코드 또는 실수로 인해 외부에서 직접 n을 접근 할 경우 의도치 않은 동작을 할 수도 있다.

이렇게 외부에서 임의로 데이터를 접근 또는 훼손하는 것을 막는 기법이 캡슐화의 데이터 은닉이다. 하지만 안타깝게도 자바스크립트에서는 문법적으로 캡슐화의 데이터 은닉를 지원하지 않는다. (java, c++에서는 문법적으로 지원한다.)

이 캡슐화의 데이터 은닉을 자바스크립트에서는 함수형 프로그래밍 기법인 클로저를 이용해 구현 할 수 있다.

1번 예제에 클로저를 적용하여 내부 변수 n을 외부에서 접근 못하게 해보자.(캡슐화의 데이터은닉)

1
2
3
4
5
6
7
8
9
10
function counter(){
var n = 0;
return {
count: function() { return n++; },
reset: function() { n = 0; }
};
}

var countA = counter();
var countB = counter();

위 소스코드에서는 counter 함수가 리턴하는 객체의 메소드 count()와 reset()가 클로저가 된다. (이부분이 이해가 안간다면 클로저의 개념과 대해 다시 공부해야 한다.)

2번 예제는 1번예제와 객체 생성방법이 다르지만, 만들어진 객체의 사용은 동일하다. count 메소드를 이용하여 n에 1을 더하고 , reset 메소드를 이용하여 n을 0으로 초기화 하고 있다.

그렇다면 2번예제에서도 1번예제처럼 직접 객체에 접근하여 n을 수정해 보자.

countA.n = 100; 을 통해 n이라는 객체에 새로운 변수를 추가 할 수 있다. 하지만 countA의 메소드(클로저)는 이 countA의 멤버변수 n을 사용하는 것이 아니라 자기 자신의 외부함수 counter의 지역변수 n을 사용하기 때문에 객체의 메소드를 호출시 계획한 대로 결과가 나오는 것을 확인 할수있다. 이것은 클로저를 통해 변수 n이 은닉되어 있기때문에 가능한 결과이다.

2번 예제를 이용해 클로저에 대한 부연설명을 조금 더 해보자면, countA.count()와 countA.reset() 메소드는 하나의 n을 공유한다. 하지만 countA 와 countB는 n을 공유하지 않고 각각의 n을 사용한다. counter함수가 호출되어 새로운 객체를 리턴 할 때마다 새로운 유효범위 체인과 새로운 내부변수(n)이 생성되기 때문이다. 여기서 이 counter의 내부변수 n이 캡슐화된 외부로부터 은닉된 실제 구현내용이 된다. 이런 캡슐화를 통해서 내부변수 n이 외부로 부터 오염되는 것을 막을수 있다.

활용2 - 함수의 호출방법의 가공

setTimeout 함수는 첫번째 인자로 받는 함수 참조값을 특정시간(두번째 인자) 이후에 실행시키는 내장함수이다. 문제는 첫번째 인자로 넘기는 함수에 매개변수를 전달 할 수 없다는 것이다. 하지만 클로저를 이용해 전달할 함수를 클로저로 변환하여 호출하는 방법을 가공 할수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sum(a, b) {
console.log("%d + %d = %d", a, b, a + b);
}

function callLater(func, a, b) {

function tempFn() {
func(a, b)
}

return tempFn;
}

var func = callLater(sum, 1, 2);
setTimeout(func, 8000);

간단한 예제이다. callLater 함수를 이용해 setTimeout에게 전달할 함수의 호출환경을 미리 설정해 놓고 그것을 클로저(tempFn)로 만들어 func 변수에 할당했다. func 클로저는 인자가 필요 없으므로 setTimeout에 첫번째 인자로 전달할 수 있다.

자바스크립트에서는 함수도 일급객체이므로 변수처럼 다른 함수의 인자로 전달하여 실행시키는 경우가 많은데, 전달할 인자 함수에 매개변수를 클로저를 통해 미리 조립하여 전달하는 방식으로 클로저를 사용 할 수 있다.

클로저의 활용 방법을 간단한 2가지 예로 설명했는데, 사실 더 다양한 방법이 있을 것이다. 스스로 더 공부하고 코딩하면서 클로저의 다양한 활용방법을 찾아보길 바란다.