클로저: closure - OOP JavaScript

from 프로자바스크립트테그닉

클로저는 어떤 함수를 감싸는 외부 함수가 종료되었더라도, 내부 함수에서 외부 함수의 변수에 접근할 수 있는 방법이다. 클로저는 강력한 동시에 복잡하다. 이 절 끝에서 언급한 사이트에 클로저를 잘 설명한 자료가 있으니 참고하길 바란다.

먼저 아래 코드에 있는 클로저에 대한 간단한 두 가지 예를 살펴 보자.



클로저가 어떻게 코드의 의미를 명료하게 만드는지 보여주는 두 가지 예

						// ID가 'main'인 엘리먼트를 찾는다.
						var obj = document.getElementById("main");

						// 테두리 스타일을 바꾼다.
						obj.style.border = "1px solid red";

						// 1초 후에 발생할 콜백을 초기화한다.
						setTimeout(function () {
							// 엘리먼트를 감춘다.
							obj.style.display = "none";
						}, 1000);

						// 일정 시간이 지난 후에 경고 메시지를 출력하는 범용 함수
						function delayedAlert (msg, time) {
							// 내부 콜백을 초기화한다.
							setTimeout(function () {
								// 둘러싸는 함수에서 넘겨받은 msg를 활용한다.
								alert(msg);	
							}, time)
						}

						// 두 개의 전달인자로 delayedAlert 함수를 호출한다.
						delayedAlert("welcome!", 2000);
						

첫 번째 함수 호출 스타일은 초보 자바스크립트 개발자들이 자주 겪는 어려움을 해결한다. 초보 자바스크립트 개발자들은 흔히 다음과 같이 코드를 작성한다.
setTimeout("otherFunction()", 1000);
// 또는 심지어...
setTimeouit("otherFunction("+num+","+num2+")", 1000);

클로저의 개념을 사용하면 코드가 이렇게 난잡해지지 않는다. 첫 번째 예는 간단하다. setTimeout을 처음 호울하고 1000밀리초 후에 콜백을 호출하지만 함수 안에서 여전히 obj 변수(전역적으로 정의한 ID가 main인 엘리먼트)를 참조한다. 두 번째로 정의한 delayedAlert는 setTimeout을 쓸 때 코드가 난잡해질만한 상황을 피하면서 함수 유효범위 안에서 클로저를 사용하는 모습을 보여준다.

간단한 형태이긴 하지만, 이렇게 클로저를 사용하면 잡탕 코드를 피할 수 있어 훨씬 이해하기 쉬운 코드를 작성할 수 있다.

이번에는 클로저로 만들 수 있는 재미있는 코드를 살펴보자. 몇몇 함수형 프로그램 언어(functional programming language)에는 커링(currying)이라는 개념이 있다. 커링이란 함수의 전달인자 몇 개를 미리 채움으로써 더 간단한 함수를 만드는 방법이다.



클로저로 함수를 커링하는 예

						
						// 숫자를 더하는 함수를 만드는 함수
						function addGenerator (num) {
							
							// 둘러싸는 함수에서 숫자 하나를 빌려와서
							// 두 숫자를 더하는 간단한 함수를 돌려준다.
							return function (toAdd) {
								return num + toAdd
							};
						}

						// 이제 addFive는 전달인자 하나를 받아서
						// 5를 더한 결과를 돌려주는 함수가 된다.
						var addFive = addGenerator(5);

						// 전달인자 4로 addFive 함수를 호출하면
						// 그 결과가 9인 것을 확인할 수 있다.
						alert(addFive(4) == 9); // true
						alert(addFive(211)); // "216"
						

자바스크립트로 코딩할 때 흔히 발생하는 문제 중 클로저로 해결할 수 있는 문제가 하나 더 있다. 초보 자바스크립트 개발자들은 실수로 전역 유효범위에 많은 변수를 남겨 두곤 한다. 전역 유효범위에 남아 있는 변수들이 다른 라이브러리에 있는 변수들과 충돌하거나, 원인을 찾기 힘든 문제를 만들 수 있기 때문에 이렇게 하는 것은 보통 좋지 않다. 아래의 코드에서 보듯이, 스스로를 실행하는 익명 함수를 사용하면 전역변수를 다른 코드로부터 숨길 수 있다.



변수들을 전역 유효범위에서 볼 수 없게 익명 함수를 사용하는 예

						
						// 포장용으로 쓸 새 익명 함수를 만든다.
						(function () {
							// 보통이라면 전역 변수로 선언되었을 변수
							var msg = "Thanks for visiting!";

							// 새 함수를 전역 객체에 연결한다.
							window.onunload = function () {
								// 숨겨진 변수를 사용한다.
								alert(msg);
							};

						// 익명 함수를 닫은 후 실행한다.
						})();
						

마지막으로, 클로저를 사용할 때 발생할만한 문제점을 하나 살펴보자. 클로저는 부모 함수 안에 있는 변수를 참조한다는 사실을 떠올려 보라. 하지만 이 경우 클로저가 사용하는 변수 값은 클로저를 만들 당시의 값이 아니다. 그 값은 부모 함수안에서 그 변수가 가지고 있던 최종 값이다. 이 점은 for 루프를 쓸 때 문제가 될 수 있다. 보통 for 루프에는 반복자(iterator)를 하나 사용한다(예를 들어 i). for 루프 안에서 클로저를 활용해 반복자를 참조하는 새 함수를 만들었다고 하자. 문제는 이 함수가 호출될 때 이 함수 안에서는 기대했던 값이 아닌, 반복자의 마지막 값(예를 들어, 배열에서 마지막 위치)을 참조한다는 사실이다. 아래의 코드는 이러한 상황을 시연하기 위해 익명 함수를 사용해 유효범위를 만드는 예이다.



클로저를 사용하는 여러 함수를 생성하는 데 필요한 유효범위를 만드는 익명 함수의 예

						
							// ID가 main인 엘리먼트
							var obj = document.getElementById("main");

							// 이 엘리먼트에 연결할 아이템 배열
							var items = ["click", "keypress"];

							// 각 아이템을 방문한다.
							for (var i=0; i<items.length; i++) {
								// 유효범위를 만들기 위해 스스로를 실행하는 익명 함수를 사용한다.
								(function () {
									// 함수를 엘리먼트에 연결한다.
									obj["on"+items[i]] = function () {
										// 다음에 나오는 변수 items[i]는 유효범위가 for 루프인
										// 부모 변수 items[i]를 참조한다.
										alert("Thanks for your "+items[i]);
									};
								})();
							}
						

클로저라는 개념은 이해하기 어렵다. 필자도 클로저가 얼마나 강력한지를 완진히 이해하는 데 많은 시간과 노력을 들여야 했다. 다행히다, 자바스크립트에서 클로저가 어떻게 작동하는지 짐 제이(Jim Jey)가 설명해 놓은 [자바스크립트 클로저(JavaScript Closures)]라는 좋은 자료가 있으니 참고하기 바란다(http://jibbering.com/faq/faq_notes/closures.html).
마지막으로 수많은 자바스크립트 객체지향 요소들의 기반인 콘텍스트라는 개념을 살펴보자.