UI Laboratory

UI 개발을 위한 레퍼런스

INDEX

자바스크립트의 함수


자바스크립트 함수의 역할

함수를 인자로 전달하거나 다른 변수에 할당하는 코드로 작성할 수 있는데 이것은 function으로 함수를 정의하는 것만으로도 함수 객체가 생성되는 것을 의미한다. 함수 객체는 일반 객체와는 다르게 '실행 코드 블록'을 갖고 있으면서도 일반 객체처럼 멤버를 가질 수도 있다.

			function f(){
				//실행 코드 블록
			}

			f.prop = "p";					//prop 속성멤버
			f.method = function(){...}		//method 메서드 멤버
		

위 코드는 함수 객체 f를 정의하는 코드로 우리가 흔히 '객체'라고 말하는 Object 객체와는 다르다. 위와 같이 동적으로 멤버를 추가할 수도 있으며 다른 함수의 인자로 전달하면 해당 함수에서 f의 prop과 method를 사용할 수 있다.

또한 함수는 new와 함께 사용되어 다른 객체를 생성하는데 사용될 수 있는데, 함수가 다른 객체를 생성할 수 있다는 것은 결국 자바스크립트에서는 함수가 일반 객체지향 프로그래밍 언어의 클래스와 유사한 역할을 할 수 있다는 의미다.

우리가 함수를 정의하면 앞에서 정의한 역할을 모두 수행할 수 있는 구조가 메모리에 정의된다.

함수 모델링

다음과 같은 add() 함수가 있다고 하자

			// add 함수 정의
			function add(x, y){
				return x + y;
			}
		

이 함수에 대한 정의가 컴파일(파싱) 단계를 거치고 나면 메모리에는 다음처럼 add 함수가 구성되어 위에서 언급한 3가지 역할을 수행하기 위한 준비를 갖추게 된다.

| 변수 스코프

함수 add가 메모리에 정의되면 위 그림처럼 기본적으로 호출 가능한 코드 블록(return x+y)이 정의된다. 그림을 보면 함수 인자로 사용ㅎㅆ던 x, y는 add함수의 지역변수로 정의되어 내부에서 접근할 수 있지만 외부에서는 접근할 수 없다. 그래서 '-'로 표시된다.

변수 스코프에는 x,y 말고도 prototype이라는 특별한 변수가 함께 정의되는데, 이 변수는 내부 코드에서도 접근할 수 있지만 외부에서도 접근할 수 있다. prototype은 다른 객체를 가리키는 참조 변수로서 그림 오른쪽을 보면 add 함수 전용 프로토타입 객체라는 것이 정의되는데, prototype은 바로 이 객체를 가리킨다. 변수 스코프는 함수를 정의할 때 사용된 변수가 정의되는 영역이다.

| 실행 코드 블록 영역

prototype을 제외하고 변수 영역에 정의된 비공개 변수는 외부에서 접근할 수 없다. 이 비공개 변수에 접근할 수 있는 방법은 실행 코드 블록에 있는 내부 코드를 통해서다. 흔히 알고 있는 함수 역할 ①은 간단히 변수 스코프와 실행코드 블록만으로도 설명할 수 있다. 따라서 다음과 같이 간단히 모델링할 수도 있다.

| 공개 변수 영역

자바스크립트 객체의 멤버는 런타임에 언제든지 추가, 제거, 대체될 수 있다. 함수도 객체로서 멤버가 동적으로 추가될 수 있는데, 이렇게 동적으로 추가되는 멤버는 함수를 정의할 때 정의되는 변수 스코프와는 다른 영역에 정의된다.

| 프로토타입 객체

함수를 정의하면 모든 함수에는 프로토타입 객체라는 것이 함께 정의된다. 객체지향 자바스크립트에서는 이 객체가 매우 중요하다.

| 영역 접근 방법

함수의 역할과 연관지어 보면 변수 스코프와 실행코드 블록은 함수 역할 ①, 즉 호출 가능한 존재로서의 역할을 하는 부분이고 공개변수 영역은 함수 역할 ②, 즉 객체로서의 함수와 연관된 부분이다. 그리고 프로토타입 객체는 다른 객체를 생성할 수 있는 요소와 연관된 영역이다.

각 영역에 접근하는 방법을 표시하면 다음과 같다.

위 그림에서 실행코드 블록 영역에서 변수 스코프로 화살표가 있는데 이것은 변수 스코프에 정의된 변수는 내부의 실행 코드에서만 접근할 수 있음을 의미한다. 그리고 프로토타입 객체는 add.prototype 처럼 함수명과 .prototype을 통해 접근할 수 있고 공개 변수 영역의 변수는 add.Description 처럼 함수명과 도트(.)를 이용해 접근할 수 있다.

함수 인자 - arguments, callee

| arguments

함수를 호출하면 자바스크립트는 호출하는 인자를 차례로 Arguments라는 타입의 객체에 배열처럼 넣어서 이 객체를 호출되는 함수에 전달한다. Arguments 타입의 객체는 함수가 호출되면 자바스크립트가 자동으로 생성하는 객체다. 함수 내부의 코드에서는 arguments라고 하는 함수의 특별한 속성을 이용해 Arguments 객체에 접근할 수 있다. 즉, 함수내부에서는 함수에 정의된 매개변수를 통해서도 접근할 수 있지만 arguments를 통해서도 전달되는 모든 인자값에 접근할 수 있다.

Arguments 객체 자체는 배열이 아니다. 그러나 각 인자에 접근할 수 있게 length 속성 및 연산자 "[]"를 지원한다. 함수 내부에서는 arguments[i]를 통해 Arguments 객체를 통해 넘어오는 인자에 접근할 수 있다.

			function sumof(){
				
				var total = 0;
				// 모든 인자를 arguments 및 length를 이용해 접근한다.

				for(var i=0; i<arguments.length; i++){
					total += arguments[i]
				}

				return total;
			}

			sumof(2,3,4);	//9 반환
			sumof(8,7);		//15 반환
		

위에서 sumof 함수에는 정의된 매개변수가 없다. 그러나 sumof를 호출할 때는 인자를 전달하고 있다. 또한 함수 내부에서도 arguments[i]를 통해 각 인자에 접근해서 인자의 총합을 반환한다.

함수에서 정의한 매개변수의 수보다 많은 수의 인자값이 전달되면 앞에서부터 차례로 받아서 매개변수에 할당하고 남은 인자값은 무시해버린다. 반대로 정해진 매개변수의 수보다 적은 수의 인자값이 넘어오면 앞에서부터 매개변수의 값이 차례로 채워지고 값을 받지 못한 매개변수는 undefined가 된다.

| callee

callee 속성은 현재 실행되고 있는 함수(함수 객체)를 나타낸다. 마치 생성자안에서 사용되는 this와도 유사한 개념이다.

arguments.callee는 익명함수에서 자신을 참조해서 재귀호출을 구현할 때 유용하게 사용할 수 있다.
			function makeFacFunc(){
				return function(x){
					if(x<=1) return 1;

					return x * arguments.callee(x-1);
				};
			}

			var result = makeFacFunc()(5);	//반환값 120 (5*4*3*2*1)
		

makeFacFunc라는 함수 내부에 x라는 이름으로 인자값을 하나 넘겨 받도록 정의돼 있는, 이름이 없는 함수가 하나 정의돼 있다. 이 내부 함수의 코드를 보면 arguments.callee를 통해 자신을 다시 호출하는 코드가 있다.

Function

| Function vs. function

Function은 함수 인스턴스를 생성하는 함수다.

인스턴스를 생성하는데 사용하는 함수를 특별히 생성자라고 하는데 Object가 Object 인스턴스(Object 생성자로 생성된 객체)를 생성하는 생성자라면 Function은 함수 인스턴스를 생성하는 생성자라고 할 수 있다.

			var add = new Function("x","y","return x+y;");
		

이 코드는 add라는 함수 인스턴스를 생성하는 표현이다. 또한 이 표현은 다음과 같이 add 함수를 정의하는 것과 동일하다.

			function add(x,y){
				return x+y;
			}
		

"함수를 정의한다는 것" 과 "함수 인스턴스를 생성한다는 것"은 표현은 다르지만 의미상으로는 거의 동일하다. 그러나 함수본문이 길어지는 경우 마지막 인자가 실행구문인 Function은 사용하기가 어렵다. 따라서 함수를 정의할 때는 Function 보다는 function을 주로 이용한다.

| Function vs. Object, Array

Object와 Array도 Function의 인스턴스다.

Function 생성자가 new와 함께 사용되어 함수 인스턴스를 생성하듯이 Object와 Array 생성자는 new와 함께 사용되어 각각 Object 객체와 배열 객체를 생성한다.

			var obj = new Object();
			var ar = new Array();
		

생성자 Object와 Array 등을 흔히 '타입' 또는 '타입 객체'라고도 표현한다. 어떻게 표현하든 Object, Array등은 결국 함수이다.

Object와 Function은 Object 객체와 함수 객체를 만들어 내는 일종의 팩토리다. Object로 생성된 객체가 Object에서 정의한 멤버를 공유하듯이 Function으로 생성된 함수는 모두 Function에서 정의한 멤버를 공유한다. Object, Array 생성자도 Function의 인스턴스로서 Function에서 정의한 기본적인 멤버를 사용할 수 있다. 즉, 자바와 같은 객체지향 언어에서 모든 객체가 Object를 상속하듯이 자바스크립트에서는 모든 함수가 Function을 상속한다.

모든 함수는 Function에서 정의하는 멤버를 공유한다.

위 그림은 Object도 함수 객체로서 Function 인스턴스이고 Object 함수를 이용하면 다시 Object 객체를 생성할 수 있음을 보여준다.

함수 객체

함수 객체는 아주 중요한 개념이다. 역할 ②는 함수가 변수에 할당될 수 있는 값으로 사용될 수 있고 함수가 다른 함수를 호출하는 인자로 사용될 수 있으며, 다른 함수의 반환값으로도 사용될 수 있다는 것이다. 즉 function(또는 Function)으로 정의된 함수는 다른 타입의 값처럼 사용할 수 있다는 의미다.

함수는 다른 타입의 값처럼 다른 함수를 호출하는 인자 반환값으로 사용될 수 있고 다른 변수에 할당될 수도 있다.
			function add(x,y){
				return x+y;
			}
		
			var add = function(x,y){
				return x+y;
			}
		

첫번째 표현은 함수 정의 코드로서 파싱단계에서 add 객체가 정의되고, 두번째 표현은 실행 코드로서 런타임에 함수가 정의된다. 정의되는 시점의 차이가 있기는 하지만 두 표현이 런타임에 만들어지는 최종적인 메모리 정의는 동일하다. 두 표현 모두 add라는 변수를 정의하고 이 변수의 값은 함수를 가리킨다.

함수 이름의 실체는 var 변수다.

| 함수의 멤버 추가

함수도 객체로서 런타임에 동적으로 속성을 추가할 수 있다. 그러나 동적으로 추가되는 변수는 함수 내부에서 접근할 수 없다.

			function add(x,y){
				alert(Description);
				return x+y;
			}

			add.Description = "I'm function";
			add(1,2);
		

위 코드를 실행하면 alert(Description)에서 "ReferenceError:Description is not defined" 에러가 발생한다.

Description은 '공개변수 영역'에 추가되는 속성으로서 add.Description처럼 함수를 통해서만 접근할 수 있다. 내부 코드에서 직접 이름을 통해 접근할 수 없다. 함수가 정의될 때 초기에 정의한 내부 변수와는 다른 영역에 저장된다고 이해하면 된다.

			function add(x,y){
				alert(add.Description);
				return x+y;
			}

			add.Description = "I'm function";
			add(1,2);
		

위와 같이 코드를 수정해서 런타임에 추가된 변수에 접근한다면 정상적으로 실행된다. 런타임에 함수 객체에 추가되는 변수는 모두 공개변수로서 (+)로 표시된다.

자바스크립트의 함수는 일반 객체처럼 런타임에 새로운 멤버를 추가할 수 있다.

| 변수 할당

함수를 다른 변수에 할당하는 것도 가능하다.

			function add(x,y){
				alert(add.Description);
				return x+y;
			}

			add.Description = "I'm function";

			var plus = add;
			var r = plus(1,2);			//3 출력
			var d = plus.Descript;		//"I'm function" 출력
		

변수에 함수를 할당하는 것이 가능하다는 것은 함수가 다른 함수를 호출할 때 해당 함수의 인자 값으로 전달될 수도 있음을 의미한다.

익명 함수

함수 리터럴을 이용해 정의된 함수를 익명 함수라고 한다. 익명 함수는 이름이 없기 때문에 주로 변수에 할당되거나 함수 인자의 값 또는 반환값으로 사용된다.

			var v = function(x,y){return x+y;};					// 함수를 정의하고 변수에 저장한다.
		

첫번째 예제는 익명 함수를 변수에 할당하는 코드로서, 이 예제를 확장하면 객체의 메소드를 정의하는 방식으로 사용할 수 있다.

			o.func(function(x,y){return x+y;});					// 함수를 정의하고 다른 함수에 인자로 전달한다.
		

두번째 예제는 다른 함수의 인자값으로 익명 함수를 정의해서 전달하는 코드다. 이 예제를 확장하면 콜백 함수 및 이벤트 핸들링 구조를 구현할 수 있다.

			var added = (function(x,y){return x+y;})(1,2);		// 함수를 정의하고 바로 호출한다.
		

세번째 예제는 익명함수를 정의한 다음 바로 호출하는 구조다. 이런 예를 확장해서 프로그램이 시작할 때 스스로 호출되는 자체 호출 함수 구조를 구현할 수 있다.

중첩 함수

			function a(arg){
				function b(in){
					return in*2;
				};
				return '결과 : ' + b(arg);
			};
		

위처럼 함수 내부에 다른 함수를 정의할 수 있는 구조도 가능한데, 이렇게 다른 함수 내부에 정의되는 함수를 중첩 함수라고 한다. b같은 내부 함수는 외부에서는 직접 호출할 수 없다. 중첩함수에서 기억해야 할 점은 내부 함수에서는 내부에 정의된 변수를 참조할 수 있다는 것이다. 외부에서는 접근할 수 없는 내부의 변수에 내부 함수는 접근할 수 있다는 사실을 이용하면 객체지향이 캡슐화와 정보 은닉이라는 매우 유익하고도 중요한 개념을 구현할 수 있다.

외부에서는 내부 변수에 접근할 수 없지만, 내부 함수는 내부 변수에 접근할 수 있다.

콜백 함수

예를 들어 라이브러리의 함수를 호출하는 프로그램이 있다고 하면 그리고 라이브러리의 함수에서 실행을 마치고 나면 다시 특정 함수(콜백함수)를 호출해주길 바란다고 하자. 이러한 구조를 콜백 구조라고 한다. 위 그림처럼 라이브러리 함수를 호출할 때 콜백될 함수에 대한 객체 참조를 전달하고 라이브러리 함수의 싷행이 끝나면 라이브러리 함수에서는 전달된 함수 객체를 호출하면 된다. 이때 다시 호출되는 메인 프로그램의 함수를 콜백함수라 한다.

			//메인 프로그램
			function MainProgram(){
				var arg;
				
				//필요한 데이터와 콜백 함수 객체를 인자로 라이브러리 함수를 호출한다.
				LibraryFunction(arg, CallbackFunction);
			};

			//콜백 함수
			function CallbackFunction(result){	//라이브러리 함수로부터 결과를 받는다
				
				//result 이용
			}

			//라이브러리 함수
			function LibraryFunction(arg, callback){	//필요한 데이터와 콜백함수를 인자로 받는다.
				var data;
				...
				...
				// 콜백함수 호출
				callback(data);
			}
		

위 코드를 익명함수를 이용해 다시 다음과 같이 구현할 수도 있다.

			//메인 프로그램
			function MainProgram(){
				var arg;
				
				//콜백함수를 익명 함수로 전달하는 구조
				LibraryFunction(arg, function(result){
					
					//result 이용
				});
			};

			//라이브러리 함수
			function LibraryFunction(arg, callback){	//필요한 데이터와 콜백함수를 인자로 받는다.
				var data;
				...
				...
				// 콜백함수 호출
				callback(data);
			}
		
			$.ajax({
				url : "...",
				dataType : "json",
				success : function(){...},
				error : function(){...}
			});
		

위 코드는 객체 리터럴{}로 만든 객체를 $.ajax() 함수의 인자로 넘겨주는 코드다. 이때 success, error 속성은 익명 함수 객체가 인라인으로 정의되어 할당되며 Ajax 호출이 성공하면 success에 할당된 함수가 콜백되고 실패하며 error에 할당된 함수가 콜백된다.

변수 스코프


변수 관리 매커니즘과 관련된 개념은 다음과 같다.

변수 스코프 → 변수 스코프 체인 → 클로저 → 클로저의 함수, 객체 생성 → 모듈 패턴 → jQuery 코드 구조

자바스크립트에서 변수를 관리하는 매커니즘의 특징적인 부분을 3가지로 정리할 수 있다.

함수 단위의 변수 관리

중괄호 단위가 아닌 함수 단위로 변수 스코프가 정의된다는 것은 함수 내부에 존재하는 if 또는 for문 코드 블럭의 내부에서 정의된 지역 변수는 해당 코드 블럭 외부에 정의된 지역 변수와 동일한 변수 스코프를 사용한다는 것이다.

			var a=1;
			function f(){
				if(true){
					var c=2;
				}
				return c;	//if문 블록에 정의된 변수 c를 반환한다.
			}
			f();	// 2반환
		

위 코드처럼 자바스크립트에서는 같은 함수 내부라면 위 코드에서처럼 if문 블록이나 for문 블록뿐 아니라 어떤 블록 내에서 정의한 변수에도 접근할 수 있다.

자바스크립트는 다른 일반 언어처럼 중괄호가 아닌 함수 단위로 변수가 관리된다.

위 코드에서 변수 a처럼 어떤 함수에도 포함돼 있지 않은 변수나 함수는 전역 변수 스코프에 정의된다. 만약 if문 블록 내부에서 var c=2; 대신 var를 없애고 c=2;로만 사용하면 어떻게 될까? var가 없으면 변수가 정의되는 것은 파싱 단계가 아니라 런타임이다. 즉, 함수 내부에서 변수를 정의하더라도 var없이 변수를 정의하면 런타임에 전역 스코프에 동적으로 변수가 정의된다. 따라서 f()의 외부 코드에서도 사용할 수 있는 전역 변수가 되어 버린다.

변수 스코프 객체

| 변수 스코프 객체

			function f(){
				var a=1;
				return a;
			}
		

위 코드처럼 함수 f가 호출되어 내부 코드가 파싱되면 다음처럼 함수 f에 대한 변수 스코프가 정의된다.

이제 실행 단계가 되면 아래 부분의 코드가 실행되고 변수 스코프에 있는 a에는 1이 할당되어 a가 반환될때 의 값은 1이 된다.

변수 스코프의 실체는 객체다. 이 변수 스코프 객체에는 몇가지 종류의 변수가 추가되는데 예를 들어, 다음과 같은 함수를 보자

			//add 정의
			function add(x,y){
				var a = x+y;
				return a;
			}

			//add 호출
			var r = add(1,2,3);
		

add(1,2,3)을 실행하면 다음과 같이 add 함수의 변수 스코프 객체가 먼저 구성된다.

변수 스코프 객체는 위 그림처럼 함수 호출시 사용된 인자 정보를 가지고 있는 arguments, 함수를 정의할 때 사용하고 있는 매개변수, 그리고 내부 변수를 (이름,값)의 쌍으로 관리하는 객체다. 함수 내부의 코드에서 변수를 사용하면 그 변수의 현재값을 찾기 위해 가장 먼저 함수 자신의 변수 스코프 객체에서 검색하게 된다.

변수 스코프 객체는 함수의 호출인자, 매개변수, 그리고 파싱 후에 얻게 되는 함수 내부 변수에 대한 값을 관리하는 객체다.

변수 스코프 객체에는 이렇듯 내부 변수 외에도 함수의 매개변수로 정의된 값과 호출하는 인자값이 모두 포함된다. 이러한 변수를 모두 그 함수의 지역 변수라고 한다.

변수 스코프 객체는 다시 말하면 해당 함수의 지역변수를 관리하는 객체다.

함수를 호출하면 자바스크립트는 동적으로 해당 함수의 변수 스코프 객체를 생성하고 함수 인자 및 매개변수, var 변수를 차례로 추가해서 해당 함수 호출과 관련된 변수 스코프 객체를 완성한다.

그런데 var가 없거나 어떤 함수에도 포함되지 않는 변수는 어떻게 될까?

			var g1 = "전역 변수#1";

			function f(){
				g2 = "전역 변수#2";
			}
		

프로그램을 실행하면 제일 먼저 변수 g1은 현재 자바스크립트 프로그램이 실행되는 환경에서의 루트 객체, 예를 들어 웹브라우저의 환경이라는 Window 객체에 추가된다. 그러고 나서 함수 f가 호출되면 그때 다시 런타임에 g2가 루트 객체에 추가된다.

| 변수 스코프 객체의 특징

			var a = 1;

			function f(){
				var b = 1;
				return a;
			}

			f(); //1 반환
			alert(b);	//b is not defined 예외 발생
		

함수의 변수 스코프에 선언된 변수는 해당 함수의 외부에서는 접근할 수 없다. 즉, f내부에 정의된 b는 함수 f외부에서는 접근할 수 없다.

렉시컬 특성

렉시컬이란 자바스크립트에서는 프로그램이 구현된 '코드'와 관련돼 있음을 의미한다. 변수를 검색할 때 함수가 실행되는 환경을 근거로 판단하는 것이 아니라 함수를 정의한 코드의 문맥을 근거로 판단한다는 것이다.

			var x = 'global';
			function f(){
				alert(x);			//'global'이 아닌 'undefined'를 출력
				var x = 'local';	// x변수 선언
				alert(x);			// 'local' 출력
			}

			f();
		

위 코드를 실행하면 함수 f내의 첫번째 alert(x)가 'global'을 출력하는 것이 아니라 'undefined'를 출력한다.

위 프로그램을 실행하면 먼저 전역 레벨의 파싱이 일어난다. 이 파싱의 결과로 전역 변수 x와 함수 변수 f가 정의된다. 그런 다음 f()를 실행하면 함수 f가 호출되고 f레벨의 파싱이 일어난다. 이 파싱의 결과로 함수 내부에 있는 x가 함수 f의 변수 스코프 객체에 정의된다.

문제는 f의 파싱이 끝나고 나서 f의 코드가 실행될 때 최초로 만난 alert(x)에서 x의 값을 찾아야 하는데, 실행 환경상에서 x를 찾을 것인지 렉시컬 환경에서 x를 찾을 것이냐이다. '렉시컬 특성'에 따라 렉시컬한 환경을 기준으로 정의된 f의 변수 스코프 객체에 정의된 x를 이용한다. 따라서 최초로 만난 alert(x)에서는 undefined가 출력된다. 다음에 x='local'이 실행되고 나서야 f의 변수 스코프에 있는 x의 값이 'local'로 변경된다.

실행시 각 문장이 참조하는 변수는 렉시컬 환경에서 정의한, 즉 '코드 그대로의 환경'을 기준으로 정의한 변수 스코프에서 검색한다.

파싱 단계와 실행 단계가 분리돼 있고 함수 단위의 렉시컬한 변수 스코프가 존재한다는 것에 주의하지 않으면 결과 예상이 빗나갈 수 있다.

변수 스코프 체인

자바스크립트에서는 중첩 함수가 가능한데, 이러한 경우 함수별로 생성되는 변수 스코프 객체 간에는 부모, 자식 관계가 만들어진다.

			var x = 1;
			function outer(){
				var y = 2;
				function inner(){
					var z = 3;
					var a = x;
				}
			}

			f();
		

변수 z는 함수 inner의 지역 변수이고 코드가 실행되면 z,a는 inner 함수의 변수 스코프 객체의 속성이 된다. y는 outer의 지역 변수로서 outer의 변수 스코프 객체의 속성이 된다. 이 때 a = x;가 실행되면 변수 x가 검색되는데, '실행문 a=x;를 포함하고 있는, 즉 정의하고 있는 함수'의 변수 스코프에서부터 먼저 검색된다. 만약 정의된 함수의 변수 스코프에서 x가 검색되지 못하면 해당 함수를 포함하고 있는 상위 함수인 outer의 변수 스코프에서 검색된다. 그곳에서도 찾지 못하면 전역 변수 스코프(global object)에서 x가 검색된다. 만약 그곳에 x가 정의돼 있지 않다면 변수가 정의되지 않았다는 에러가 발생한다. 이렇게 변수 스코프 간의 관계를 변수 스코프 체인이라고 한다.

변수 검색이 가능한 영역은 변수가 정의된 함수의 변수 스코프와 부모 함수를 포함한 조상 함수의 변수 스코프다.

함수 매개변수도 해당 함수의 변수 스코프에 정의된다. 그럼 다음과 같은 변수 접근도 가능하다.

			function outer(count){
				inner();
				function inner(){
					return counter--;
				};
			}
		

루트 객체

전역 변수 스코프를 나타내는 객체를 루트 객체라고 하며 '전역 객체'라고 표현하기도 한다.

런타임에 전역 변수 스코프의 코드에서 this를 이용하면 루트 객체에 정의된 변수 및 속성, 함수를 사용할 수 있다.

전역변수 스코프의 코드에서의 this는 루트 객체를 참조한다.

루트 객체에 정의된 속성과 함수의 대표적인 예는 다음과 같다.

			//전역 속성 정의
			this.Infinity
			this.undefined

			//내장객체 정의
			this.Array = function(...){...}
			this.Function = function(...){...}

			//전역함수 정의
			this.parseInt = function(...){...}
			this.encodeURI = function(...){...}
		

이처럼 내장 객체는 전역변수 스코프에서 정의된 함수다. 그러나 전역 변수 스코프에 정의된 함수 및 속성을 코드에서 사용할 때는 this를 생략할 수 있다.

			var o = new this.Object() → var o = new Object();
		

이 실행문이 정의된 함수의 변수 스코프 객체에서 Object를 찾지 못하면 체인을 거슬러 올라가서 결국 전역 변수 스코프 객체에서 찾아서 사용한다.

사실 전역적인 실행환경, 즉 함수의 영역이 아닌 어떤 함수에도 속하지 않는 최상위 영역에서 변수를 선언하거나 함수를 정의하면 그것들은 모두 루트 객체의 속성과 메서드로 추가된다.

			var a = 1, b =2;
			this.prop1 = "a";
			function init(obj){
				var c;
			};
		

위에서 a, b 그리고 prop1, init은 모두 어떤 함수에도 속하지 않는, 즉 전역 영역에서 정의되는 변수로서 루트 객체의 속성과 메서드로 추가된다.

웹페이지 실행환경에서의 루트 객체는 Window 객체로서 코드에서는 Window를 통해 접근할 수 있다. 따라서 전역 영역의 코드에서는 this와 window는 같은 객체를 참조한다.

클로저

클로저라는 개념은 자바스크립에는 없는 class의 역할을 대신해 비공개 속성/메서드, 공개 속성/메서드를 구현할 수 있는 근거를 마련한다. 따라서 객체지향적인 특징인 캡슐화와 정보 은닉을 이해하려면 클로저를 반드시 이해해야 한다.

| 자바스크립트 클로저

자바스크립트 함수는 객체로서 다른 함수의 반환값으로 사용될 수 있다고 했다. 이 경우 반환값으로 사용된 함수의 변수 스코프의 문제를 생각해 보자

			function outer(){
				var x = 0;
				return function(){
					return ++x;
				};
			}

			//코드 실행
			var x = -1;
			var f = outer();
			f();	//1 반환
		

위 코드는 ①내부 함수가 익명함수로 되어 outer의 반환값으로 사용됐다. ②inner는 outer의 실행 환경에서 실행된다. ③inner에서 사용하는 변수 x는 outer의 변수 스코프에 있다.

이 프로그램이 실행되면 var f = outer()에 의해 파싱 단계에서는 outer의 내부에서 정의됐던 익명 함수가 실행 단계에서는 outer의 외부로 전달되어 실행된다.

실행 환경에 있는 f를 통해 outer가 반환한 익명함수가 호출되면 return ++x;에서 사용된 변수 x를 어디에서 검색할까? 원래 정의될 때 생성된 익명 함수의 변수 스코프 객체에서 검색할 까? 답은 런타임의 변수는 렉시컬 환경을 기준으로 정의된 변수 스코프 및 체인에서 검색한다는 것이다.

프로그램 실행 시 변수 검색은 해당 문장(return ++x)이 포함된 함수가 정의된 렉시컬 환경에서의 변수 스코프 체인을 기준으로 한다.

결국 앞의 예제 코드에서 inner를 호출하면 ++x 연산에 의해 1이 반환된다. 문제는 함수 inner를 계속해서 호출해서 결과를 보면 다음과 같다.

			f();	//1 반환
			f();	//2 반환
			f();	//3 반환
			f();	//4 반환
		

내부 함수에서 선언된 변수가 아니면서 내부 함수에서 사용하는 outer의 x같은 변수를 자유변수라고 한다. x가 메모리에서 제거되는 시기는 outer가 결정하지 못한다. 이런 자유 변수는 outer가 실행되고 있는 환경이 '닫는(close)' 역할을 한다. 즉 x의 경우는 변수 스코프가 outer가 실행되는 환경으로까지 확장된다. 외부환경에서 내부 함수에 대한 참조 f를 가지고 있는 이상 outer 함수는 '실행 중' 상태가 된다. 따라서 자유 변수 x 및 해당 변수 스코프 체인 관계는 메모리에서 계속 유지된다. 이처럼 outer 호출이 종료되더라도 outer의 지역 변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를 클로저라고 한다.

함수 호출이 종료되더라도 그 함수의 지역변수 및 지역변수 체인 관계를 유지할 수 있는 구조를 클로저라 한다.

자유 변수의 경우 그 값은 렉시컬 환경이 영향을 받으면서 그 생명주기는 실행 환경의 영향을 받는다는 것이 결국 클로저를 만들 수 있는 근거가 된다.

마지막으로 Function으로 생성한 함수는 클로저를 만들지 못한다. 그 함수는 항상 전역 영역에서 생성된 것처럼 컴파일된다.

			var x = 'g';
			function f(){
				var x = "1";
				return new Function("return x");	//x를 전역 변수 스코프에서 검색한다.

			}

			var global = f();

			alert(global());						//'g'를 출력
		

| 클로저 인스턴스

클로저가 생성한 인스턴스라는 의미로 '클로저 인스턴스'라고 표현을 하자. 이것은 클로저를 인스턴스를 생성하는 단위로 보겠다는 것이다. 클로저란 것은 내부 함수를 반환값으로 사용하는 특수한 함수로 볼수 있다. 즉, 클로저를 함수 인스턴스를 만들어내는 특수한 함수로 해석할 수 있다는 것이다. 일반 객체지향 프로그래밍 언어의 클래스 같은 존재와 비교해 클로저를 '함수를 생성하는 클래스'로 비유적으로 표현할 수 있다.

앞에서 본 outer 함수를 클래스로 생각해 보자.

outer 클로저를 이렇게 비공개 변수를 정의하는 부분과 외부에서 호출이 가능한 공개 영역으로 나눠서 생각해보면 다른 언어의 클래스와 더욱더 유사해 보일 것이다. 이제 'outer를 호출'하는 것을 바로 '함수 객체를 생성'하는 것으로 생각하면 된다.

			var f = outer();	//outer의 인스턴스를 생성
		
클로저를 호출하는 것은 '클로저 인스턴스를 생성'하는 것이다.

클로저가 반환한 함수 f를 호출하는 것을 outer가 외부에 공개한 메서드를 호출하는 것으로 간주할 수 있다.

			f();	//outer의 공개함수 사용
		
클로저 인스턴스를 호출하는 것은 클로저가 외부에 공개한 멤버를 호출하는 것으로 이해할 수 있다.

'f가 사라지지 않는 이상 인스턴스 f가 가지고 있는 변수도 계속 유지된다'.

			var f = outer();
			f();	//1 반환
			f();	//2 반환
			var g = outer();
			g();	//1 반환
			g();	//2 반환
		

outer()를 호출해서 생성된 함수를 f에 할당한다. f를 호출해서 값을 1 증가시키면 다음에 f를 호출될 때는 이전에 증가된 값이 유지되어 두번째 호출의 시작값이 된다. 함수 f 호출이 종료돼도 내부 변수 x는 그대로 유지되는 클로저의 속성을 이용하고 있다.

중요한 것은 outer()를 호출해서 새로운 인스턴스를 g에 할당한 후 g를 호출해서 결과를 보면 '내부 변수 x가 새롭게 초기화' 됐음을 알 수 있다. 내부 변수 x가 새롭게 초기화됐다는 것은 이전의 함수 f와 새롭게 생성된 함수 g는 전혀 다른 변수 공간을 사용하는 별도의 존재라는 것이다.

클로저를 호출하면 단순히 익명함수가 반환되는 것이 아니라 익명 함수와 함께 거기에 연결된 닫혀진 공간이 함께 반환되는 것이다. 그 닫혀진 공간에 내부 변수가 존재한다.

f와 g는 이제 독립적인 변수 공간을 가진 인스턴스다. 클로저 인스턴스는 단순히 클로저에서 반환된 함수에 대한 참조가 아니다. 클로저는 위 그림처럼 독립된 변수 공간을 가진 인스턴스를 반환하는 것이다. 이것이 클로저의 진정한 의미다.

클로저란 비공개 내부 변수를 갖는 함수 인스턴스 생성자다. 그리고 클로저로 생성한 독립된 변수 공간을 가진 인스턴스를 클로저 인스턴스라고 한다.

자바스크립트 객체


클래스 기반의 객체지향

| 객체

객체지향에서는 상태를 속성이라 하고 행동을 메서드라고 한다. 즉 모든 객체는 속성과 메서드로 구성된다. 객체지향에서는 속성과 메서드를 모두 객체의 멤버라고 한다.

| 클래스(Class)

객체지향 프로그래밍에서 클래스란 객체를 만들어 내는 청사진 또는 프레임 또는 객체를 만들어 내는 레시피라고 할수 있으며, 동일한 클래스를 사용해 다른 객체를 만들어 낼 수 있다.

| 캡슐화(Encapsulation)

캡슐화는 객체지향 프로그래밍과 관련된 또 다른 개념으로서 객체지향 언어는 하나의 객체는 관련 속성과 메서드를 하나의 객체로 묶어 포함할 수 있음(encapsulate)을 나타낸다.

캡슐화와 함께 나오는 개념으로 정보 은닉(information hiding)이 있다. 이는 외부에 노출된 메서드 또는 속성을 코드에서 사용할 뿐 내부적으로 메서드, 속성이 어떻게 구현됐는지는 사용자에게 숨긴다. 즉, 객체의 구현이 어떻게 됐는지를 상관하지 않고 객체가 노출하는 속성과 메서드로만 작업을 할 수 있다.

객체지향에서는 내부 구현을 숨길 수 있을 뿐더러 메서드, 속성 자체도 외부에 노출시키거나 감출 수 있다. 이것은 정보은닉의 또 다른 특징이다. 자바스크립트에서는 모든 메서드, 속성은 공개(public)로 정의된다. 그러나 비공개(private) 특징을 구현하는 방법도 있다.

| 집합(Aggregation)

몇 개의 객체를 합쳐서 하나의 객체로 만드는 것을 집합이라고 한다. 이러한 집합 개념은 문제를 좀 더 작고 관리할 수 있는 부분으로 나눠서 분리할 수 있는 방법이다. 객체지향 언어에서는 이러한 분할/정복, 그리고 종합하는 과정을 지원할 수 있어야 하며 자바스크립트도 이런 집합 특징은 기본적으로 지원한다.

| 상속(Inheritance)

객체지향 프로그래밍에서의 상속이란 이미 존재하는 코드를 재사용하거나 기존 코드를 확장하는 좋은 방법이다. 객체가 다른 객체를 상속할 때 보통 새로운 메서드 또는 속성이 추가된다. 이런 것을 표현하기 위해서 '상속'이라는 표현 대신 '확장'이라고도 표현한다. 부모 객체를 상속하면서 부모의 메서드와 속성을 다시 정의하는 경우도 있다. 즉 메서드명, 속성명은 동일하나 자식 객체의 메서드나 속성을 호출하면 다른 방식으로 작동하도록 상속된 멤버를 다시 정의하는 것을 오버라이딩이라고 한다.

일반 객체지향 언어에서는 클래스 차원에서 상속을 지원하지만 자바스크립트에서는 클래스 상속은 없다. 대신 프로토타입을 이용한 객체 차원의 속성을 지원한다.

| 다형성(Polymorphism)

예를 들어 Person 객체의 모든 속성과 메서드를 상속받은 Korean 객체를 만들었다고 하자. 즉 Person 객체나 Korean 객체 모두 'talk' 메서드를 제공하지만 Person에서 제공하는 'talk'메서드와 Korean에서 제공하는 'talk' 메서드의 구현 내용은 다를 수 있다. 이때 dalbong2라는 변수에는 Person 객체가 할당될 수도 있고 Korean 이라는 객체가 할당될 수도 있다. 이처럼 동일한 이름의 메서드를 호출하더라도 실제 호출 대상이 되는 객체에 따라 서로 다른 메서드가 실행되는 것을 다형성이라고 한다. 정리하면 다형성은 여러 객체가 동일한 메서드 호출에 반응하는 능력을 말한다.

객체 생성

| 객체 생성자

객체 지향은 말 그대로 객체를 기반으로 하므로 객체지향 언어는 객체를 생성할 수 있는 방법이 있어야 한다. 자바스크립트에서는 함수가 다른 언어에서의 클래스를 대신해서 객체를 정의하고 생성하는데 사용된다. 객체를 생성하는 데 사용하는 함수를 특별히 생성자(constructor)라고 한다.

| 객체의 정의 및 생성

자바스크립트에서 동일한 멤버를 가진 객체를 만들어내는 방법이 3가지가 있다.

  1. 1) new 와 Object 생성자 이용 - new Object()
  2. 2) 객체 리터럴 이용 - {}
  3. 3) new와 사용자 정의 생성자 이용 - new Person()

이 가운데 1),2)는 자바스크립트에 내장돼 있는 Object 객체의 구조를 정의하고 생성하는 방법이다. 그리고 3)은 사용자가 객체를 생성하는 함수를 직접 정의해서 객체를 생성하는 방법이다.

| 자바스크립트 내장(built-in) 생성자

Object라는 함수는 자바스크립트에서 기본적으로 제공해주는 생성자다. 이렇게 자바스크립트에서 기본적으로 제공해주는 생성자는 Object 말고도 많다.

String, Number, Boolean, Array, Date, Function, Object, RegExp, Math 등

이 같은 함수를 생성자라고 부르는 대신 '내장 타입' 또는 '내장 객체(Built-in Object)'라고 하고 기본적으로 이것들이 함수라는 점을 생각하면 '내장 함수'라고 해도 될 것이다. 중요한 것은 표현의 문제가 아니라 모두 생성자의 역할을 해서 객체의 초기 구조를 정의하고 있고 객체를 생성하는데 사용할 수 있다는 것이다.

프로그램이 시작되면 자바스크립트 엔진은 미리 정의해 놓은 함수를 기본적으로 메모리에 정의를 한다. 이러한 내장함수는 개발자의 편의와 각기 특별한 목적을 위해 자바스크립트에서 특별히 부여한 기능을 지닌 멤버가 사전에 정의되어 더해져 있을 뿐 일반함수가 지닌 모든 특성을 그대로 가진다.

내장 생성자도 함수 모델을 그대로 따른다.

| 함수와 클래스의 차이점

함수가 객체를 생성하는 역할을 하지만 그렇다고 클래스와 동일한 개념은 아니다. 먼저 함수와 클래스 기반으로 하는 타입 시스템이 다르다. 클래스를 사용하는 언어는 강력한 타입의 언어다. 그러나 자바스크립트 언어에서는 함수에 전달되는 객체가 어떤 타입인지 중요하지도 않고 타입 체크도 하지 않는다.컴파일(파싱) 단계에서는 메서드의 존재 여부를 알 수 없다.

자바스크립트의 타입 시스템에서는 객체에 속성과 메서드가 존재하는지 여부는 해당 객체의 타입을 통해서가 아니라 실제 런타임에 호출해봐야 알 수 있다.

자바스크립트에서는 객체를 생성한 후에 객체의 멤버를 추가, 제거, 대체하면서 구조를 런타임에 변경해서 최종적으로 원하는 객체의 구조를 만들어 낼수 있다. 런타임에 멤버를 동적으로 추가/제거할 수 있다는 것은 자바스크립트의 기본적인 특징이다.

자바스크립트에서는 객체의 구조를 런타임에 언제든지 변경할 수 있다.

Object 객체 정의 I - new Object

자바스크립트에서는 객체를 생성하기 위해 함수를 호출할 때 new를 사용한다. 'Object 객체'라고 말하면 'Object 생성자를 이용해서 만들어진 객체'라는 의미다.

Object, Array 등 내장 객체의 본질은 함수다.

Object 함수에는 자바스크립트 객체에 필요한 기본적인 멤버가 정의돼 있는데, 좀 더 엄밀하게 말하면 Object의 '프로토타입 객체'에 정의돼 있다. Object처럼 미리 제공되는 생성자의 경우 멤버는 이미 정의돼 있기 때문에 정의 단계는 건너뛰고 객체를 생성하는 단계만 수행하면 된다.

			var mySon = new Object();	// new 연산자 사용
		

mySon 객체에 필요한 멤버가 있다면 다음처럼 추가하면 된다.

			mySon.Name = '달봉이';
			mySon.Age = 8;
			mySon.IncreaseAge = function(i){this.Age+i;};
		

Object객체 정의 Ⅱ - 객체 리터럴

객체 리터럴은 Object 객체의 구조를 정의하고 생성하는 구문을 하나로 합칠 수 있어서 new와 Object 생성자를 이용해 객체를 생성하고 필요한 구조를 만들어가는 과정을 좀 더 간소화할 수 있다.

			var mySon = {
				Name : '달봉이',
				Age : 7,
				InceaseAge : function(i){this.Age +1};
			}
		
객체 리터럴을 사용해 객체를 생성하는 방법은 내부적으로 new Object를 수행한 후 멤버를 구성하는 방법과 동일한 절차를 따른다.

이렇게 정의된 멤버는 모두 외부에서 접근할 수 있는 공개멤버이다. 이 방법으로는 비공개 멤버를 구현할 수 없다.

객체 리터럴 표현에서 사용하는 this는 {}로 생성되는 객체를 가리킨다. 따라서 this.Age는 mySon.Age와 동일한 표현이 된다.

객체 리터럴를 이용해 다음과 같은 방법으로도 만들 수 있다.

			var mySon = {};
			mySon.Name = '달봉이';
			mySon.Age = 7;
			mySon.increaseAge = function(i){this.Age +1};
		

다음과 같은 중첩된 표현도 가능하다.

			var teacherName = "이주영";
			var mySon = {
				Name : "달봉이",
				Age : 7,
				Parent : {
					Name : "홍길동",
					Job : "Freelancer"
				},
				Etc : this.Name + "의 선생님 이름:" + teacherName
			};
		

Parent의 job 속성에 대해서는 다음과 같이 접근하면 된다.

			mySon.Parent.Job
		

jQuery 같은 라이브러리를 보면 new Object보다 Object 리터럴 표현을 자주 사용하고 있음을 알 수 있다.

아래처럼 함수를 호출할 때 전달할 인자값도 객체 리터럴 표현을 이용할 수 있다.

			function OpenWindow(options){
				var url = options.path || 'http://dalbong2.net';
				var name = options.windowName || 'dalbong2window';
				var option = options.windowOptions || 'width=100,height=100';
				window.open(url, name, option);
			}

			var options = {path : 'http://dalbong2.net', windowName : 'dalbong2Window', windowOptions : 'width=500,height=500'};
			OpenWiindow(options);
		

함수 내부에서는 논리연산자(||)를 이용하여 인자를 전달받는 options에서 path, windowName, windowOptions 속성이 있는지 체크한다. 없으면 각 기본값을 이용한다.