Closure

From. Effective Javascript - 아이템 11 "클로저에 익숙해져라"

클로저(closure)는 이를 지원하지 않는 언어를 사용하다 넘어온 프로그래머에게는 친근하지 않은 개념일 수도 있고, 처음에는 위협적으로 보일 수도 있다. 하지만 나머지 사람들에게는 클로저를 마스터하기 위한 노력이 분명한 보상으로 되돌아 올 것이라고 확신한다.

다행히도 실제로는 전혀 걱정할 것이 없다. 클러저를 이해하기 위해서는 단지 세 가지 기본적인 사실만 배우면 된다.
첫 번째로 자바스크립트는 현재 함수 외부에서 선언된 변수를 참조할 수 있다.

							function makeSandwich() {
								var magicIngredient = "peanut butter";
								function make(filling) {
									return magicIngredient + " and " + filling;
								}
								return make("jelly");
							}
							makeSandwich(); // "peanut butter and jelly"							
						

내부의 make 함수가 이 함수 바깥에서 선언된, 다시 말해서 makeSandwich 함수에서 선언된 magicIngredient 변수를 참조한다는 사실을 주목하라.

두 번째로, 함수는 외부 함수가 무언가를 리턴한 이후에도 이 외부 함수에 선언된 변수를 참조할 수 있다.
이 사실을 이해하기 힘들다면, 자바스크립트 함수가 일종 객체(first-class object)라는 사실을 기억하라(아이템 19 참고). 이는 내부 함수를 리턴할 수 있고, 이 함수가 나중에 다시 호출될 수 있다는 뜻이다.

							function sandwichMaker() {
								var magicIngredient = "peanut butter";
								function make(filling) {
									return magicIngredient + " and " + filling;
								}
								return make;
							}
							var f = sandwichMaker();
							f("jelly"); // "peanut butter and jelly"
							f("bananas"); // "peanut butter and bananas"
							f("marshmallows"); // "peanut butter and marshmallows"				
						

이 예제는 외부 함수 안에서 make("jelly")를 곧바로 호출하는 대신에 sandwichMaker가 make 함수 자체를 리턴한다는 점을 빼고는 첫 번째 예제와 거의 동일하다. f의 값은 내부의 make 함수이고, f를 호출하는 것은 make를 효과적으로 호출하는 셈이 된다. 하지만 sandwichMaker가 이미 리턴되었더라도(즉 종료), make 함수는 어찌되었든 magicIngredient의 값을 기억하고 있다.

이런 일이 어떻게 가능할까? 자바스크립트 함수 값은 호출되었을 때 실행되기 위한 코드 뿐만 아니라 더 많은 정보를 포함하고 있기 때문에 가능하다. 자바스크립트 함수는 해당 스코프에서 선언되어 참조할 수 있는 어떤 변수더라도 내부적으로 보관한다. 함수 자신이 포함하는 스코프의 변수들을 추적하는 함수를 클로저라고 한다. make 함수는 magicIngredient와 filling 두 개의 외부 변수(1개의 외부변수와 1개의 내부변수 아닌가?)를 참조하는 클로저다. 언제든 make 함수가 호출되면, 이 두 변수가 클로저에 저장되어 있기 때문에 참조할 수 있다.

함수는 파라미터와 외부 함수의 변수뿐만 아니라 해당 스코프 내에 포함된 어떤 변수라도 참조할 수 있다. 이를 이용하면 더 보편적으로 사용할 수 있는 sandwichMaker 함수를 만들 수 있다.

							function sandwichMaker (magicIngredient) {
								function make (filling) {
									return magicIngredient + " and " +filling;
								}
								return make;
							}

							var hamAnd = sandwichMaker("ham");
							hamAnd("cheese"); // "ham and cheese"
							hamAnd("mustard"); // "ham and mustard"

							var turkeyAnd = sandwichMaker("turkey");
							turkeyAnd("Swiss"); // "turkey and Swiss"
							turkeyAnd("Provolone"); // "turkey and Provolone"		
						

이 예제는 hamAnd 와 turkeyAnd 두 개의 다른 함수를 생성한다. 두 함수가 동일한 make 정의에 의해 만들어짐에도 불구하고, 이들은 두 개의 서로 다른 객체다. 첫 번재 함수에서 magicIngredient의 값은 "ham"이고, 두 번재 함수에서는 "turkey"다.

클로저는 자바스크립트에서 가장 우아하고 표현력이 높은 기능 중 하나이고, 많은 유용한 코딩 관례들의 중심이 된다. 자바스크립트는 클로저를 생성하기 위한 더 편리하고 일반적인 문법을 제공하는데, 함수 표현식이 바로 그것이다.

							function sandwichMaker (magicIngredient) {
								return function (filling) {
									return magicIngredient + " and " + filling;
								}
							}
						

함수 표현식이 익명인 사실에 주목하라. 이 함수는 지역적으로 호출하기 위한 의도로 만든 것이 아니라, 새로운 함수 값을 만들기 위해 평가하는 용도로만 만들어졌기 때문에 이름을 지을 필요조차 없다. 물론 함수 표현식은 이름을 가질 수도 있다(아이템14 참조).

세 번째이자 마지막으로 기억해야 할 사실은 클로저가 외부 변수의 값을 변경할 수 있다는 점이다.
클로저는 실제로 외부 변수의 값을 복사하지 않고 참조를 저장한다. 따라서 그들에게 접근하는 어떤 클로저도 변경사항을 볼 수 있다. 이 내용을 묘사하는 간단한 코딩 관례로 box 객체에 대한 다음 예제를 살펴보자. box 객체는 내부의 값을 가지며, 그 값을 읽고 변경할 수 있는 객체이다.

							function box() {
								var val = undefined;
								return {
									set: function(newVal) { val = newVal;},
									get: function() {return val;},
									type: function() {return typeof val;}
								};
							}

							var b = box();
							b.type();	// "undefined"
							b.set(98.6);
							b.get();	// 98.6
							b.type();	// "number"
						

이 예제는 세 개의 클로저, 즉 set, get, type 프로퍼티들을 포함하는 객체를 생성한다. 각 클로저는 val 변수를 공유하여 접근한다. set 클로저로 val 값을 변경하고, 그 이후에 get과 type을 호출해 변경에 대한 결과를 확인한다.