'closure'에 해당되는 글 1건

  1. 2013.08.28 [번역] Explaining Javascript Scope and Closure

이번 포스트에서는 초보 프로그래머들이 다서 어려워하는 개념인 클로저(Closure)에 대한 내용을 다루도록 하겠습니다. 

사실 '자바스크립트 클로저'라고 검색을 하면 수많은 Q&A와 포스트들을 찾을 수 있는데요, 그 많은 글들을 읽어보아도 완벽하게 손에 쥘 듯 이해되지는 않는 듯 합니다. 저도 마찬가지였구요. 그 이유는 크게 두 가지인 것으로 생각됩니다. 하나는, 클로저와 객체의 스코프(영역, Scope)를 통합하여 이해하지 못하기 때문이고, 다른 하나는 클로저라는 것이 왜 클로저라고 불리는지 이해하지 못하기 때문입니다. 실제로 클로저를 능숙하게 사용한다는 자바스크립트 프로그래머들에게 클로저가 왜 클로저라고 불리느냐 물어보면 제대로 대답하지 못하는 경우가 많더군요. 

어쨌든, 이 글을 통해 클로저와 스코프에 대해 명확히 이해할 수 있기를 기대하며 시작해보도록 하겠습니다. 이 글의 원문 주소는  http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/ 입니다.



이 글에서는 많은 사람들이 어려워하는 개념에 대해 다룰까 합니다. 바로 자바스크립트의 유효 범위(scope)와 클로저(closure)에 대한 내용입니다.

 

1. 배경 (Background)

이미 매우 많은 기사나 블로그에서 자바스크립트의 유효범위와 클로저에 대해 다루었지만, 필자가 생각하기에 그들 대부분 명백하고 확실했던 것 같지는 않았습니다. 게다가 그 글들 중 상당수는 사람들이 적어도 15개 정도의 다른 프로그래밍 언어를 경험한 것으로 간주하는 것 같았습니다. 하지만 제 경험상 자바스크립트를 개발하는 대부분의 사람들은 CJava 같은 언어가 아니라 HTMLCSS를 먼저 접하고 개발을 시작했었습니다.

하여, 제 최종 목표는 유효범위와 클로저가 무엇인지, 어떻게 동작하는지, 그리고 어떤 점이 좋은지에 대해 모든 사람들이 명확하게 이해할 수 있도록 돕는 것입니다. 물론, 이 글을 읽기 전에 변수(variables)나 함수(functions)의 개념에 대한 기본적인 이해는 필요할 것입니다.

 

2. 유효범위 (Scope)

유효범위라 함은, 변수나 함수가 접근 가능한 영역, 혹은 어떤 맥락(context)이 실행되는 곳을 의미합니다.  기본적으로 변수나 함수는 전역(global scope)이나 지역(local scope)적으로 선언될 수 있습니다. 변수들은 소위 함수영역(function scope)라고 불리는 유효범위를 갖고 있고, 함수들도 변수로서 동일한 유효범위를 갖고 있습니다.


- 전역 범위(Global Scope)

 무엇이든 전역적이다 라고 말한다면, 그것이 코드의 어느 곳에서든 접근 가능하다는 것을 의미합니다. 다음 예제를 확인해 보시기 바랍니다.


var monkey = "Gorilla";

function greetVisitor () {
	return alert("Hello dear blog reader!");
}

 

이 코드가 브라우저 내에서 실행된다면, 이 함수의 유효범위는 window 객체가 될 것입니다. 그러므로 해당 브라우저에서 실행되는 모든 코드에서 접근 가능할 것입니다.


- 지역 범위(Local Scope)

 전역 범위와는 다르게 지역 범위라고 하면, 함수와 같이 코드의 어떤 부분에서만 접근이 가능하게 정의되었다는 것을 의미합니다. 예를 들어 봅시다;


function talkDirty () {
	var saying = "Oh, you little VB lover, you";
	return alert(saying);
}
alert(saying); // Throws an error


위 코드에서, saying이라는 변수는 talkDirty라는 함수 내에서만 접근할 수 있습니다. 이 함수의 외부에서는 정의된 적이 없죠. 주의할 점은, 위의 2 라인에서 sayingvar 지시자 없이 선언하면 이 변수는 자동적으로 전역변수가 된다는 것입니다.

이것이 의미하는 바는, 함수 내에 중첩된 함수가 자신을 포함하고 있는 함수의 변수와 함수에 접근할 수도 있다는 것입니다:


function saveName (firstName) {
	function capitalizeName () {
		return firstName.toUpperCase();
	}
	var capitalized = capitalizeName();
	return capitalized; 
}
alert(saveName("Robert")); // Returns "ROBERT"


위 코드에서 볼 수 있듯, capitalizeName 함수에는 어떠한 입력 파라미터도 없습니다만, 그를 포함하고 있는 함수인 saveName의 파라미터-firstName에 접근할 수 있습니다. 좀 더 명확히 알기 위해 예제를 하나 더 보기로 하겠습니다.


function siblings () {
	var siblings = ["John", "Liza", "Peter"];
	function siblingCount () {
		var siblingsLength = siblings.length;
		return siblingsLength;
	}
	function joinSiblingNames () {
		return "I have " + siblingCount() + " siblings:\n\n" + siblings.join("\n");
	}
	return joinSiblingNames(); 
}
alert(siblings()); // Outputs "I have 3 siblings: John Liza Peter"


여기서 알 수 있듯이, 두 개의 내부 함수 모두 siblings라는 지역 변수에 접근할 수 있고, 또 각각의 내부 함수들은 서로 접근할 수 있습니다.(여기서는 joinSiblingNames 함수가 siblingCount 함수에 접근하고 있습니다.) 하지만 siblingLength라는 변수는 siblingCount라는 함수 내에서만 접근할 수 있는데, 이것이 이 변수의 유효범위인 것입니다.

 

3. 클로저 (Closure)

실행범위에 대해 좀 더 잘 이해했을테니, 이제 거기에 클로저에 대한 개념을 섞어봅시다. 클로저라는 것은 (보통은 함수인) 표현식인데, 어떤 실행 맥락 내의 변수 집합과 연동할 수 있는 것을 의미합니다. 혹은, 좀 더 쉽게 설명한다면, 외부 함수의 지역 변수를 참조하는 내부 함수들은 클로저를 만들게 됩니다. 예를 들면 다음과 같습니다.


function add (x) {
	return function (y) {
		return x + y;
	};
}
var add5 = add(5);
var no8 = add5(3);
alert(no8); // Returns 8


와우, 도대체 무슨 일이 일어난 걸까요? 한 번 자세히 살펴봅시다.

1. add 함수가 호출되었을 때함수가 하나 리턴됩니다.

2. 이 함수는 현재의 실행 맥락(context)을 폐쇄(close)하고 그것이 실행될 때의 파라미터 x의 값을 기억합니다. (위 코드에서는 5입니다.)

3. add함수의 실행 결과가 add5 변수에 할당되었을 때이 변수도 최초 초기화 되었을 때의 값을 알고 있게 됩니다.

4. add5 변수는 항상 입력된 값에 5를 더하는 함수를 참조하게 된 것입니다.

5. , add5에 입력 파라미터로 3을 이용하면, 5 3을 더하여 8을 리턴하게 됩니다.


, 자바스크립트 세계에서 add5 함수는 다음과 같이 동작하게 될 것입니다.


function add5 (y) {
	return 5 + y;
}


- 악명높은 반복문(Loop) 문제

i라는 값을 원하는 방식 대로 할당하기 위해 얼마나 많이 반복문을 만들어 봤습니까? , 엘리먼트들에 입력된 i들이 모두 마지막 값만 리턴하는 것을 본 적이 있습니까?


+ 잘못된 참조

먼저 잘못된 코드를 살펴보기로 합시다. 이 코드에서는 다섯 개의 a 엘리먼트를 생성하고 거기에 각각의 i 에 해당하는 값을 텍스트(‘Link’+i)로 추가하고 alert로 그 i값을 출력하는 onclick 이벤트를 추가합니다. 아마도 다음과 같은 모양이 될 것 같습니다.


function addLinks () {
	for (var i=0, link; i<5; i++) {
		link = document.createElement("a");
		link.innerHTML = "Link " + i;
		link.onclick = function () {
			alert(i);
		};
		document.body.appendChild(link);
	}
}
window.onload = addLinks;


각각의 엘리먼트들은 원하는 대로 “Link0”, “Link1”등의 텍스트를 포함하게 됩니다. 하지만 각각을 클릭했을 때에는 모든 엘리먼트들이 5를 출력하는 것을 볼 수 있을 것입니다. 도대체 무슨 일일까요? 그 이유는, i라는 변수가 반복문 내에서 매번 1씩 증가하는데, onclick 이벤트가 호출되지 않았으므로, 매번 더해진 값이 각각의 엘리먼트에 적용되는 것입니다.

그러므로 반복문은 i 5가 될 때 까지 반복될 것인데, 그 값은 addLink라는 함수가 종료되기 직전의 마지막 값입니다. 그래서, onclick 이벤트가 호출된다 하더라도 i의 마지막 값인 5가 출력됩니다.


+ 실행 참조

이를 위해 클로저를 생성하여 i 값을 설정할 때, 설정하는 순간의 i 값이 onclick 이벤트에 적용되도록 해야 합니다. 다음과 같이 말이죠.


function addLinks () {
	for (var i=0, link; i<5; i++) {
		link = document.createElement("a");
		link.innerHTML = "Link " + i;
		link.onclick = function (num) {
			return function () {
				alert(num);
			};
		}(i);
		document.body.appendChild(link);
	}
}
window.onload = addLinks;


이 코드를 실행하면, 첫 번째 엘리먼트는 0, 두 번째는 1우리가 정확히 원했던 방식대로 출력됩니다. 여기서 사용한 해결책은 onclick 이벤트에 적용되는 내부 함수가 num이라는 파라미터를 입력받는 클로저를 생성하는 것입니다. 그리고 여기에 그 시점의 i가 이용됩니다.

그 뒤 이 함수는 i 값을 안전하게 간수한 뒤 폐쇄(close)되며 호출될 때 최초 입력되었던 값을 그대로 리턴할 수 있게 됩니다.

 

- 자기 실행 함수 (Self-Invoking Function)

자기 실행 함수는 즉시 실행되는 함수를 말하는데, 그를 위해 자기 자신이 클로저를 생성합니다. 다음을 살펴봅시다.


(function () {
	var dog = "German Shepherd";
	alert(dog);
})();
alert(dog); // Returns undefined


알겠지만, dog이라는 변수는 자신의 맥락 내에서만 사용이 가능하도록 숨겨져 있습니다. 숨겨진 개(dog)라니놀랍군요. 하지만 정말 놀라운 부분은 이제부터 시작인데요, 이 방식으로 위에서 다뤘던 반복문 문제를 해결했다는 점입니다. 그리고 이것은 Yahoo Javascript Module Pattern의 기반이기도 합니다.


+ Yahoo Javascript Module Pattern

이 패턴의 요지는 클로저를 생성하는 자기 호출 함수를 이용한다는 것인데, 그 결과 private이나 public 속성/메소드를 정의할 수 있게 됩니다. 다음의 간단한 예를 살펴보죠:


var person = function () {
	// Private
	var name = "Robert";
	return {
		getName : function () {
			return name;
		},
		setName : function (newName) {
			name = newName;
		}
	};
}();
alert(person.name); // Undefined
alert(person.getName()); // "Robert"
person.setName("Robert Nyman");
alert(person.getName()); // "Robert Nyman"


  이 코드의 아름다운 점은 이제 우리가 객체에 대해 어떤 것은 public으로 접근하게 하고, 어떤 것은 private으로 만들어 외부에서 함부러 바꾸지 못하게 할 수 있다는 것입니다. 위에 정의되어 있는 name이라는 변수는 이 함수의 외부에서는 접근이 불가능하지만, name 변수에 대한 참조를 갖고 있는 클로저를 생성하기 때문에 getName 함수와 setName 함수를 이용하여 값을 얻거나 변경할 수 있게 됩니다.

 

4. 결론

필자의 순수한 희망은, 초급이든 숙련된 개발자이든 간에, 자바스크립트 내에서 유효범위와 클로저가 어떻게 동작하는지 명확하게 이해하는 것입니다. 질문과 피드백은 언제든 환영합니다. 여러분들이 제공해 주신 어떤 내용이든 중요하다고 생각되면, 이 글에 지속적으로 반영하도록 하겠습니다.



이해가 잘 되셨나요? 원문에 쓰여 있는 대로, 질문과 피드백은 언제든 환영합니다. 즐거운 코딩 되시길~

신고

'일하기 > JAVASCRIPT' 카테고리의 다른 글

[번역] Explaining Javascript Scope and Closure  (0) 2013.08.28
Posted by Layered 트랙백 0 : 댓글 0

댓글을 달아 주세요