UI Laboratory

UI 개발을 위한 레퍼런스

INDEX

  1. arguments 객체를 이용하여 인수의 수를 비교해서 서로 다른 경우에 에러를 되돌려 주는 예
  2. 인수의 수가 정해져 있지 않은 가변 인수의 함수 정의하기
  3. 재귀 호출을 위한 callee 프로퍼티

XHTML 문서에서 <script> 태그를 이용할 때의 주의점


XHTML에서 <!-- ~ -->와 같은 표기법을 사용하면 스크립트 부분이 주석으로 인식되어 무시되어 버릴 가능성이 있다. 그러므로 페이지 내에 스크립트를 기술하고 싶은 경우 아래와 같이 기술한다.

			<script type="text/javascript">
				//
			</script>
		

<![CDATA[ ~ ]]>로 감싸인 블록을 CDATA 섹션이라고 한다. CDATA 섹션 안의 텍스트는 해석의 대상으로 간주되므로 < 나 &와 같은 문자열도 자유롭게 사용할 수 있다.

데이터형


기본형과 참조형

기본형의 변수에는 값 그 자체가 직접 보관되는데에 반해 참조형의 변수는 그 참조값(값을 실제로 보관하고 있는 메모리의 어드레스)을 보관한다. 그로 인해 기본형과 참조형에 따라 데이터의 취급방식이 틀리다.

리터럴

리터럴이란 데이터형에 보관되는 값의 표현방법을 말한다. 숫자리터럴, 문자리터럴, 배열 리터럴의 세가지가 있다.

| 숫자 리터럴(number)

숫자 리터럴은 정수 리터럴과 부동소수점 리터럴의 두가지로 분류된다.

정수 리터럴은 10진수 리터럴, 8진수 리터럴, 16진수 리터럴로 분류되며 8진수/16진수로 표현하고자 한다면, 리터럴의 앞에 각각 『0』, 『0x』를 붙여야 한다. 8진수에서는 0~7의 값을, 16진수에서는 0~9의 값과 더불어 A(a)~F(f)까지의 영문자를 사용할 수 있다.

| 객체 리터럴(object)

객체란 이름을 키로 하여 접근이 가능한 배열이다. 해시 또는 연상배열이라고도 한다.

배열 내의 개별데이터는 '요소'라 불리는데 반해, 객체 내의 개별 데이터는 '프로퍼티'라고 불린다. 프로퍼티에는 문자열이나 수치등의 정보는 물론, 함수를 대입하는 것도 가능하다. 함수가 대입된 프로퍼티를 특별히 메소드라고 부른다.

			var obj = {x:1, y:2, z:3};
			document.writeln(obj.x);	// 1
			document.writeln(obj['x']);	// 1
		

객체 리터럴이 개별 프로퍼티에 접근하기 위한 방법으로, 닷(.)연산자에 의한 방법과 괄로구문에 의한 방법이 있다. 닷 연산자에서는 식별자의 명명규칙을 따르지 않는 123과 같은 이름을 사용할 수 없다. 그러나 괄호구문에서는 프로퍼티명을 어디까지나 문자열로 지정하므로 이러한 제한이 없다.

			obj.123		// X (잘못된 예)
			obj['123']
		

| 함수 리터럴(function)

javascript에서는 함수도 데이터형의 하나로 취급한다.

| 미정의값(undefined)

어떤 변수가 선언완료 상태에서 값을 부여하지 않은 경우나 미정의된 프로퍼티를 참조하려고 하는 경우 undefined를 반환한다.

			var v = 1;
		

변수를 선언하면 먼저 undefined 값이 자동으로 할당된다. 위 코드에서 변수 v는 먼저 undefined로 정의되고 코드를 실행하는 단계에서 다시 1로 초기화 된다. undefined는 '값이 정해지지 않음'이라는 의미를 나타내기 위해 특별히 정의한 값이다.

값이 할당되지 않은 변수, 할당되지 않은 함수 매개변수, 그리고 할당되지 않은 객체의 멤버는 undefined로 초기화된다.

			var v1;
			if(v1){
				//실행코드
			}
		

위 코드에서는 if문에서 undefined가 불린값으로 변환된다. v1의 값은 undefined이고 불린값을 요구하는 if문의 조건식에서 undefined는 false로 판정되기 때문에 if문의 코드블록은 실행되지 않는다.

| null

null은 '객체가 없음'을 나타내는 특별한 값이다. 즉, null은 코드를 통해 명시적으로 할당하는 값으로서 undefined와는 구분되는 값이다.

숫자와 연산


산술 연산자

| 가산 연산자(+)

가산연산자는 피연산자의 데이터형에 따라 다르게 동작하므로 주의해야 한다.

			alert('10'+1);	// 101
			var today = new Date();
			alert(1234+today);	// 1234Mon Jul 02 2012 23:04:13 GMT+0900
		

위와 같이 피연산자가 문자열인 경우 + 연산자가 연결연산자로 인식되어 101이라는 결과를 낳게 된다. 또한 피연산자가 객체인 경우도 객체를 문자열 형식으로 변환한 다음, 문자열이 연결된다.

| 증가 연산자(++)와 감소 연산자(--)

증가/감소 연산자에서 연산한 결과를 다른 변수에 대입하는 경우에는 주의가 필요하다. 증가/감소 연산자를 피연산자의 전후 어느 쪽에 놓는가에 따라 결과가 다르게 된다.

			​var x = 3;
			var y = x++;
			alert('x = '+x);	//x = 4;
			alert('y = '+y);​	//y = 3;
		

위와 같이 증가/감소 연산자를 피연산자의 뒤에 두면 변수 x를 변수 y에 대입한 후에 변수 x의 증가를 행한다. 이를 후치연산이라고 한다.

			​var x = 3;
			var y = ++x;
			alert('x = '+x);	//x = 4;
			alert('y = '+y);​	//y = 4;
		

반면, 증가/감소 연산자를 피연산자의 앞에 두면 변수 x를 증가한 후에 그 결과를 변수 y에 대입하는데 이를 전치연산이라고 한다.

비교 연산자

| 항등연산자(==)와 완전항등연산자(===)

==연산자와 ===연산자 모두 주어진 두 값이 같은지 확인하는데 쓰이지만, '같다'는 것을 정의하는 기준이 서로 다르다.

| 완전항등연산자(===)

===연산자는 일치 연산자로 알려져 있는데, '같다'는 기준을 매우 엄격하게 정의하여 두 피연산자가 '일치'하는지 확인한다.

====연산자가 두 값이 일치하는지 아닌지 판단하는 기준은 다음과 같다.

			"1" == true		//false를 반환한다.
		

위 표현식은 false이다. 두값은 타입이 다르므로 서로 일치하지 않는다.

| 항등연산자(==)

==연산자는 동등 연산자로 알려져 있는데, '같다'는 것을 말할 때 타입 변환도 허용하는 좀 더 느슨한 정의에 입각하여 두 피연산자가 '동등'한지 확인한다.

==연산자가 두 값이 동등한지 아닌지 판단하는 기준은 다음과 같다.

			"1" == true		//true를 반환한다.
		

위 표현식은 true이다. 위 표현식에선 먼저 불리언 값 true가 숫자 1로 변환된 후 다시 비교된다. 이어서 문자열 "1"이 숫자 1로 변환된다. 이제 두 숫자는 같은 숫자이기 때문에 비교식의 결과로 true를 반환한다.

			var ary1 = ['javascript','ajax','asp.net'];
			var ary2 = ['javascript','ajax','asp.net'];

			alert(ary1==ary2);		// false
		

그러나 비교의 대상이 배열이나 객체 등의 이른바 참조형인 경우에는 주의가 필요하다. 기본형은 변수에 값을 직접 보관한 반면, 참조형에서는 참조값(메모리상의 어드레스)이 보관되어져있다. 항등연산자에서 참조형을 비교하는 경우에는 참조값, 즉 메모리상의 어드레스가 동일한 경우에만 true가 반환된다. 보기에는 동일한 내용을 포함하고 있는 객체라고 해도 그것이 다른 객체(다른 어드레스로 등록된 것)라면, 항등연산자는 false를 반환한다.

논리 연산자

| 논리AND 연산자(&&)와 논리OR 연산자(||)

&&연산자의 경우 좌측이 false라고 판정된 시점에서 조건식 전체가 반드시 false가 되므로 우측 식은 실행되지 않는다.

			var x=1;
			​x==1 && document.writeln('안녕하세요');​	//안녕하세요
		

위 예에서 변수x가 만약 1이 아니라면 좌측식이 false가 되어 &&연산자의 우측 식이 실행이 되지 않는다. 여기서는 변수 x가 1이므로 좌측식인 document.writeln 명령을 실행한다.

||연산자에서도 동일하여 ||연산자의 좌측이 true인 경우, 조건식 전체가 반드시 true가 되므로 우측식은 실행되지 않는다.

그외의 연산자

| delete 연산자

delete 연산자는 배열 요소나 객체의 프로퍼티를 삭제한다. 삭제에 성공했을 경우에 true를, 실패한 경우에는 false를 반환한다.

			var ary = ['JavaScript', 'Ajax', 'ASP.NET'];
			document.writeln(delete ary[0]);	// true
			document.writeln(ary);				// , 'Ajax', 'ASP.NET'
			var obj = {x:1, y:2};
			document.writeln(delete obj.x);		// true
			document.writeln(obj.x);			// undefined
			var obj2 = {x:obj, y:2};
			document.writeln(delete obj2.x);	// true
			document.writeln(obj);				// [object object]
			var data1 = 1;
			document.writeln(delete data1);		// false
			document.writeln(data1);			// 1
			data2 = 10;	
			document.writeln(delete data2);		// true
			document.writeln(data2);			// 에러(data2는 존재하지 않는다.)
		

위 예에서 알수 있듯이

| typeof 연산자

typeof 연산자는 단항연산자로 이 연산자의 값은 피연산자의 데이터 타입을 가리키는 문자열이다.

주어진 피연산자가 숫자, 문자열 또는 불리언 값일 경우 typeof 연산자의 결과도 그에 따라 "number", "string" 또는 "boolean"이 된다. 객체, 배열 그리고 null에 대해서는 어느쪽이든 똑같이 결과로 "object"를 반환한다는 점에 주의해야 한다. 함수타입의 피연산자에 대해서는 "function"을, 그리고 정의되지 않은 피연산자에 대해서는 "undefined"를 반환한다.

피연산자로 Number, String 또는 Boolean 포장(wrapper)객체가 주어진다면 typeof의 결과는 "object"가 된다. 또한 Date나 RegExp 객체에 대한 결과도 "object"가 된다.

제어 명령


switch 명령

X, Y, Z의 모든 값에 일치하는 블록을 표현하고 싶은 경우에는 아래 예와 같이 공백의 case 블록을 기술한다.

			var rank = 'B';
			switch(rank){
				case 'A' :
				case 'B' :
				case 'C' :
					document.writeln('합격!');
					break;
				case 'D' :
					document.writeln('불합격!');
					break;
			}
		

이 예에서는 변수 rank가 A, B, C인 경우에 '합격!'이라는 메시지를, D의 경우에는 '불합격'이라는 메시지를 각각 표시한다.

for...in 명령

			for(가변수 in 배열/객체){
				루프 내에서 실행하는 명령
			}
		

가변수에는 배열/연상배열이나 객체로부터 취한 요소의 인덱스 번호나 키명, 멤버명이 대입되어 for...in 블록안에서 요소 값을 참조할 때에 사용할 수 있다. 여기에서 가변수에 대입된 것이 요소 값 그 자체가 아님을 주의해야 한다.

for...in 루프를 이용하는 것은 연상배열, 객체의 키를 조회할 경우에 한하며, 일반 배열을 조회할 경우는 원칙적으로 for 루프를 이용하도록 한다.

루프를 도중에 Skip 또는 중단하기

특정 도전을 만족하는 경우에 루프를 강제적으로 중단하고 싶은 경우에는 break 명령을 사용한다.

위 예에서는 변수 i를 1~100 사이에서 더해나가 합계가 1000을 넘을 때에 루프를 빠져나오고 있다. 이렇게 break 명령은 if 명령과 함께 사용하는 것이 일반적이다.

한편 루프를 완전히 중단해 버리는 것이 아니라 현재의 루프만을 건너뛰고 다음 루프를 계속해서 실행하고 싶은 경우에는 continue 명령을 사용한다. 아래의 예는 변수 i를 1~100 사이에서 홀수만 더하여 그 합계를 구하는 예제이다.

예외 처리 - try...catch...finally 명령

스크립트 전체가 정지해 버리지 않도록 하는 것이 예외 처리의 역할이다.

객체


javascript의 객체란 이름과 키로 접근 가능한 배열이다. 하지만 단순히 이름이 붙은 그릇의 집합이 아닌 객체 자체가 하나의 개체이며, 내부에 포함된 요소는 그 개체의 특성이나 동작을 나타내기 위해 존재한다. 이렇게 객체의 상태나 특성을 나타내기 위한 정보가 프로퍼티이며 객체를 조작하기 위한 도구가 메소드이다. 다시말해 객체는 프로퍼티와 메소드로 구성되어 있으며 데이터를 조작하기 위한 여러 가지 기능을 가지고 있다.

객체를 이용하기 위한 준비 - new 연산자

객체의 복제를 만드는 것을 인스턴스화, 인스턴스화에 의해 만들어진 복제본을 인스턴스라고 부른다. 인스턴스화란 객체를 취급하기 위해서 '자기 자신 전용의 영역'을 확보하는 행위라 할 수 있다.

객체를 인스턴스화하는 데에는 new 연산자를 이용한다.

			var 변수명 = new 객체명(인수,...)
		

객체를 초기하하기 위해서 객체에는 객체와 동일한 이름의 메소드가 준비되어 있다. 이 초기화 메소드를 생성자라고 부른다.

생성된 인스턴스는 변수에 보관되며 인스턴스 변수에서 프로퍼티/메소드를 호출하려면 아래와 같이 닷구문을 사용한다.

			변수명.프로퍼티명 = 설정값;
			변수명.메소드명(인수);
		

정적 프로퍼티/정적 메소드

프로퍼티나 메소드에 따라 예외적으로 인스턴스를 생성하지 않고 바로 이용이 가능한 것도 있다. 이러한 프로퍼티/메소드를 정적 프로퍼티/정적 메소드라고 한다. 정적 프로퍼티/정적 메소드를 호출하기 위한 구문은 아래와 같다.

			객체명.프로퍼티명 = 설정값;
			객체명.메소드명(인수);
		

내장형 객체

내장형 객체로는 Global, Object, Array, String, Boolean, Number, Function, Math, Date, RegExp, Error 가 있다. 이중 Global, Object, Array, String, Boolean, Number, Function까지는 javascript의 데이터형과 대응하고 있다.

			var str = '안녕하세요';
		

객체를 이용하려면 인스턴스화라는 절차를 밟아야 하나 위 구문처럼 javascrit에서는 리터럴을 그대로 대응하는 내장형 객체로서 이용할 수 있으며 인스턴스화를 거의 의식할 필요가 없다.

기본 데이터형이라도 new 연산자를 사용하여

			var str = new String('안녕하세요');
		

와 같이 명시적으로 객체를 생성할 수 있으나 기본 데이터형을 new 연산자를 사용하여 인스턴스화하는 것은 원칙적으로 피해야 한다.

래퍼 객체
javascript의 표준적인 데이터형을 다루는 내장형 객체들 중에도 특별히 기본형인 문자열, 숫자, 논리값을 취급하기 위한 객체를 래퍼 객체라고 부른다. 래퍼 객체란 '단순히 값에 지나지 않는 기본헝의 데이터를 포장해서(래핑한) 객체로서의 기능을 추가하기 위한 객체'를 말하며 Number 객체, String 객체, Boolean 객체가 있다. javascript 에서는 기본 데이터형과 객체로서의 외견을 갖춘 래퍼 객체를 자동적으로 상호 호환하기 때문에 이를 의식할 필요가 없다.

기본 데이터를 취급하기 위한 객체

| String 객체

String 객체는 문자열형의 값을 취급하기 위한 래퍼 객체이다.

			var str = new String('안녕하세요');
		

new 연산자를 이용하여 명시적으로 생성할 수 있으나 통상적으로 리터럴 표현을 사용하여 아래와 같이 기술한다.

			var str = '안녕하세요';
		
String객체에서 이용 가능한 주요 멤버
분류 멤버 개요
검색 indexOf(substr, [,start]) 문자열 전방(start+1번째 문자)부터 부분문자열 substr을 검색
lastIndexOf(substr, [,start]) 문자열 후방(start+1번째 문자)부터 부분문자열 substr을 검색
부분문자열 charAt(n) n+1번째의 문자를 추출
slice(start, [,end]) 문자열부터 start+1~end번째 문자를 추출
substring(start, [,end]) 문자열부터 start+1~end번째 문자를 추출
substr(start, [,cnt]) 문자열부터 start+1번째 문자부터 cnt 수만큼의 문자를 추출
split(str, [,limit]) 문자열을 분할문자열 str로 분할하여 그 결과를 배열로 취득
문자수식 anchor(name) 문자열을 Anchor화(<a name="name">을 적용)
link(url) 문자열을 link화(<a href="url">을 적용)
그 외 concat(str) 문자열 뒤쪽에 문자열을 str을 연결
length 문자열의 길이를 취득
■ 부분 문자열을 추출할 때의 두 가지 주의점

String 메소드에는 원본 문자열로부터 부분 문자열을 추출하기 위한 메소드로 substring/slice/substr의 세가지 메소드를 제공한다. 그중 substring/slice와 substr메소드의 차이점은 아래와 같다.

| Number 객체

Number 객체는 수치형의 값을 취급하기 위한 래퍼 객체이다.

			var num = new Number(123);
		

new 연산자를 이용하여 명시적으로 생성할 수 있으나 통상적으로 리터럴 표현을 사용하여 아래와 같이 기술한다.

			var num = 123;
		
■ 10진수를 16진수로 변환하기

Number객체의 toString 메서드를 사용하면 된다.

			var num = 255;
			alert(num.toString(16)); //10진수 255와 같은 16진수 값 ff 출력
		

기본적으로 자바스크립트의 숫자는 10진수이다. 그러나 숫자를 16진수나 8진수로 만들어 사용할 수 있다. 16진수 숫자는 0x로 시작하며, 8진수 숫자는 0으로 시작한다.

아래는 웹색상을 랜덤하게 생성한다.

			function randomVal(val){
				return Math.floor(Math.random()*val);
			}

			function randomColor(){
				return "rgb(" + randomVal(256) + "," + randomVal(256) + "," + randomVal(256) + ")";
			}
		

| Math 객체

Math객체는 생성자가 없으며 인스턴스를 생성할 수도 없다. 새로운 Math 인스턴스를 생성하는 대신 객체의 속성과 메서드에 직접 접근해야 한다.

			var maxValue = Math.max(firstValue, secondValue);	//둘 중 더 큰 숫자 반환
		
■ Math의 정적 메서드

아래는 0부터 255사이의 랜덤한 숫자를 생성한다.

			var randomNum = Math.floor(Math.random()*256);
		

random 메서드를 사용해서 0부터 1사이의 랜덤한 숫자를 구한 후 256를 곱하고 floor 메서드를 사용해서 정수로 만든다.

아래는 5부터 10까지의 랜덤한 숫자를 구한다.

			var randomNum = Math.floor(Math.random()*6)+5;
		

| Array 객체

Array 객체는 배열형의 값을 취급하기 위한 객체이다. Array 객체는 리터럴 표현을 사용하여

			var ary = ['사토', '다카에', '나카타'];
		

와 같이 생성할 수 있으나, 생성자 경유로 다음과 같이 생성하는 것도 가능하다.

			var ary = new Array('사토', '다카에', '나카타');	//지정요소로 배열을 생성
			var ary = new Array();	//공백의 배열을 생성
			var ary = new Array(10);	//지정 사이즈로 공백의 배열을 생성
		

배열을 생성할 때에는 가능한한 배열 리터럴을 이용한다. 공백의 배열을 생성하려면 다음과 같이 기술한다.

			var ary = [];
		
Array객체에서 이용 가능한 주요 멤버
분류 멤버 개요
가공 concat(ary) 지정 배열을 현재의 배열에 연결
join(del) 배열 내의 요소를 구분문자 del로 연결
slice(start, [,end]) start~end-1번째 요소를 빼냄
splice(start, cnt, [,rep [,...]]) 배열 내의 start+1~start+cnt+1번째의 요소를 rep, ...로 치환
추가/삭제 pop() 배열 끝의 요소를 취득하여 삭제
push(data) 배열 끝에 요소를 추가
shift() 배열 선두의 요소를 취득하여 삭제
unshift(data1, [,data2, ...]) 배열 선두에 지정 요소를 추가
정렬 reverse() 역순으로 정렬(반전)
sort([fnc]) 요소를 오름차순으로 정렬
기타 length 배열의 사이즈
toString() '요소, 요소, ...'의 형식으로 문자열로 치환
■ concat()

Array.concat() 메서드는 본래 배열의 모든 원소에 concat() 메서드의 전달인자들을 전부 이어붙인 배열을 새롭게 생성하여 반환한다. 만약 concat() 메서드의 전달인자로 배열을 전달하면, 이 배열 안의 원소들을 꺼내어 반환하는 배열에 이어 붙인다. 원래 배열의 값은 변경하지 않는다.

			var a = [1,2,3];

			a.concat(4,5);			// [1,2,3,4,5]를 반환한다.
			a.concat([4,5]);		// [1,2,3,4,5]를 반환한다.
			a.concat([4,5],[6,7]);	// [1,2,3,4,5,6,7]을 반환한다.
		
■ unshift()와 shift()

unshift()와 shift() 메서드는 배열의 맨 앞에서 원소를 삽입하거나 제거한다.

unshift()
unshift() 메서드는 하나 혹은 그 이상의 원소들을 배열의 맨 앞에 삽입한다. 삽입을 수행한 후, 배열의 새로운 길이를 반환한다.
			var a = [];				// a:[]

			a.unshift(1);			// a:[1]				배열의 길이 1을 반환한다.
			a.unshift(22);			// a:[22, 1]			배열의 길이 2을 반환한다.
			a.unshift(3,[4,5]);		// a:[3, [4,5], 22, 1]	배열의 길이 4를 반환한다.
		
shift()
shift() 메서드는 배열의 첫번째 원소를 제거한 후, 제거한 원소를 반환한다.
			var a = [22, 1];		// a:[22, 1]

			a.shift();				// a:[1]	22를 반환한다.
			a.shift();				// a:[]		1을 반환한다.
		

unshift()와 shift() 메서드는 배열의 맨 앞에서 원소를 삽입하거나 제거한다.

■ Array 객체의 멤버에서 주의해야 할 세가지

Date 객체

| Date 객체를 생성하기

Date 객체에는 문자열이나 배열 등과 같이 리터렬 표현이 존재하지 않으며 객체의 생성에 반드시 생성자를 경유해야 한다.

			var d = new Date();		// 디폴트의 Date객체 생성
			var d = new Date('2010/12/04');		// 날짜 문자열 값을 이용하여 Date 객체를 생성
			var d = new Date(2010, 11, 4, 23, 55, 30, 500);		// 년월일/시분초/밀리초의 형식으로 지정. 시문초, 밀리초는 생략 가능
		

| 날짜/시각 데이터를 가산/감산하기

Date 객체에는 날짜/시각을 직접 가산/감산하기 위한 메소드가 없다. 이러한 계산을 위해서는 아래와 같이 한다.

  1. 1. getXxxx 메소드에서 현재의 날짜/시각 요소를 취득해 둔다.
  2. 2. 취득한 값에 가산/감산을 한다.
  3. 3. 위 2의 결과를 setXxxxx 메소드로 다시 설정을 한다.

아래는 그 달의 마지막 날을 구하는 예이다. '다음 달의 0일째'는 Date 객체에서 이 달의 마지막 날로 인식된다.

Object 객체

| Object 객체란?

Object 객체는 다른 객체에 대하여 객체의 공통적인 성질/기능을 제공한다. 다시 말해 Object 객체는 모든 객체의 기본 객체다. '객체'라고 이름 붙은 모든 것은 Object 객체에 정의된 프로퍼티나 메소드를 공통으로 이용할 수 있다.

■ 객체를 기본형으로 변환하기 - toString/valueOf 메소드 -

toString/valueOf 메소드는 각각 객체의 내용을 기본형의 값으로 변환한다.

위 예에서 보듯, toString/valueOf 메소드는 대부분의 객체에서 동일한 값을 반환해 준다. 유일하게 Date객체만이 다른 값을 돌려준다.

참고로, javascript는 객체를 문자열로 변환할 필요가 있는 문맥에서 자동적으로 toString 메소드를 호출한다. 예를 들어, document.writeln 메소드, + 연사자등의 경우는 명시적으로 toString 메소드를 호출하지 않아도 암묵적으로 문자열 표현으로 변환한다.

■ 인스턴스의 객체형을 판단하기 - constructor 프로퍼티 -

typeof 연산자는 어디까지나 기본형을 식별하는 것만이 가능하다. 참조형인 Array, Object, Date 객체의 어느 인스턴스라고 해도 typeof 연산자는 한결같이 'object'로 밖에 돌려 주지 않는다.

참조형 변수를 식별하려면 constructor 프로퍼티를 사용한다.

			var data = [];
			if(typeof data == 'object' && data.constructor == Array){
			   document.writeln('변수 data는 Array 객체의 인스턴스다.');  //변수 data는 Array 객체의 인스턴스다.
			}
		

constructor 프로퍼티는 반환값으로서 인스턴스의 생성에 사용된 생성자를 돌려준다. 객체명(문자열)을 돌려주고 있는 것이 아니기 때문에 아래와 같이 따옴표로 둘러싸면 안된다.

			data.constructor == 'Array'
		

| 익명 객체 작성하기

Object 객체를 직접 인스턴스화함으로써 사용자가 자기 자신의 객체를 정의하는 데에 사용할 수도 있다.

			var obj = new Object();
		

이러한 객체를 익명객체라고 하며 아래와 같이 프로퍼티를 추가할 수 있다.

			obj.name = '홍길동';
			obj.birth = new Date(2005, 7, 15);
			obj.old = 5;
		

익명 객체는 한정된 장소에서 일시적으로 데이터를 사용하거나 향후 재이용되지 않는 작은 규모의 구조 데이터를 주고 받을 시에 사용할 경우 기술한다. 단, 일시적인 데이터를 주고받는 목적으로 많이 이용되는 객체에 메소드를 지정할 기회는 많지 않다.

			var obj = {name:'홍길동', birth:new Date(2005, 7, 15), old:5};
		

익명 객체는 위와 같이 객체 리터럴로 바꿔 작성할 수 있다. '객체 리터럴로 객체를 생성하기'란 실은 '익명 객체를 정의하기'와 같은 의미이다.

Global 객체

| Global 객체와 그 멤버

글로벌 객체란 이른바 글로벌 변수나 글로벌 함수를 관리하기 위해 Javascript가 자동적으로 생성하는 편의적인 객체이다.

| parseFloat / parseInt / Number 함수 - 수치 값으로서의 명시적인 변환

Javascript는 문맥 상황에 따라 적절한 데이터형으로 자동 변환해준다. 그러나 이 자동 변환이 때로는 생각지 못한 버그의 온상이 되기도 한다. 이러한 이유로 Javascript에서는 데이터형을 명시적으로 변환하기 위한 방법을 제공하고 있다. Number, parseFloat / parseInt 함수는 모두 주어진 값을 수치로 변환한다.

Number 함수와 Number 객체

글로벌 객체가 제공하는 글로벌 함수 Number의 정체는, 실은 내장형 객체 Number이기도 하다. 따라서 아래는 같은 결과를 얻을 수 있다.

						document.writeln(Number(123));
						document.writeln(new Number(123));
					

데이타형을 명시적으로 문자열형, 논리형으로 변환하고 싶은 경우도 마찬가지로 각각 String/Boolean 함수를 사용한다. Number 함수와 같이 String/Boolean 함수의 실체는 String/Boolean 객체이며, 아래의 어떤 작성법을 사용해도 의미는 같다.

			var str = new String('123');
			var str = String('123');
		
■ 산술연산자에 의한 문자열/수치로의 변환
			document.writeln(typeof(123+''));	//String
			document.writeln(typeof('123'-0));	//number
		

+ 연산자는 주어진 피연산자중 한쪽이 문자열인 경우에 다른 한쪽도 자동적으로 문자열로 변환한 뒤에 연결한다.

- 연산자에서는 주어진 피연산자 중 한쪽이 수치인 경우에 다른 한쪽도 자동적으로 수치로 변환한 후에 감산을 행한다.

| 동적으로 생성한 스크립트 실행하기 - eval 함수 -

eval 함수는 주어진 문자열을 Javascript의 코드로서 평가/실행한다.

			var str = 'window alert("eval 함수")';
			eval(str);		// eval 함수
		

eval 함수는 아래와 같은 이유로 남용은 피해야 한다.

함수


함수를 정의하는 세가지 방법

사용자정의 함수를 정의하는 방법에는 크게 3가지 방법이 있다.

function 명령으로 정의하기

			function triangle(base, height){
				return base * height / 2;
			}
			document.writeln('삼각형의 면적:' + triangle(5,2));	//5
		

반환값이 없는 함수에서는 return 명령을 생략해도 상관없다. return 명령이 생략되었을 경우, 함수는 디폴트로 undefined를 돌려준다.

Function 생성자 경유로 정의하기

Javascript 에서는 내장형 객체로서 Function 객체를 준비하고 있다. 함수는 이 Function 객체의 생성자를 이용하여 정의할 수 있다.

			var 변수명 = new Function([인수1 [,인수2[,...,]]], 함수의 본체);
		
			var triangle = new Function('base','height','return base * height / 2;');
			document.writeln('삼각형의 면적:' + triangle(5,2));	//5
		

new 연산자를 생략하고, 마치 글로벌 함수인 것처럼 기술할 수도 있다.

			var triangle = Function('base','height','return base * height / 2;');
		

Function 생성자에는 function 명령에는 없는 중요한 특징이 있다. 그것은 Function 생성자에는 인수나 함수본체를 문자열로 정의할 수 있다.

			var param = 'height, width';
			var formula = 'return height * width / 2;';
			var diamond = new Function(param, formula);

			document.writeln('마름모의 면적:' + diamond(5,2));	//5
		

스크립트상에서 문자열을 연결하고 인수/함수 본체를 동적으로 생성할 수도 있다.

그러나 굳이 Function 생성자를 이용해야 할 장점이 없다. Function 생성자는 실행시 호출될 때마다 코드의 해석에서부터 함수 객체의 생성까지 전부 실행되기 때문에 성능 저하의 한 요인이 될 수 있다.

그러므로 Javascript의 함수는 기본적으로 function 명령 또는 함수 리터럴로 정의한다.

함수 리터럴 표현으로 정의하기

Javascript에 있어서의 함수는 데이터형의 일종이다. 즉, 함수를 변수에 대입한다든지, 어떤 함수의 인수로서 건네준다든지, 혹은 반환값으로서 함수를 건네주는 것 조차 가능하다.

			var triangle = function(base, height){
				return base * height / 2;
			}

			document.writeln('삼각형의 면적:' + triangle(5,2));	//5
		

함수 리터럴은 선언한 시점에서는 이름을 가지지 않기에 익명함수라고도 불리며 매우 중요한 개념이다.

함수 정의에 있어 네가지 주의점

| return 명령은 도중에 개행하지 않는다.

| 함수는 데이터형의 일종

			var triangle = function(base, height){
				return base * height / 2;
			}

			document.writeln(triangle(5,2));	//5
		

Javascript에서는 '함수는 데이터형의 일종'이다. 위예에서 triangle 함수를 정의한다는 것은 실은 triangle이라고 하는 변수에 함수형의 리터럴을 대입하는 것과 동일하다.

| function 명령은 정적인 구조를 선언한다.

			document.writeln(triangle(5,2));	//5

			function triangle(base, height){
				return base * height / 2;
			}
		

'함수 정의가 변수 정의다'라는 전제에 근거한다면, 위예는 에러가 되어야 한다. 함수를 호출하는 시점에서 아직 triangle 함수는 선언되지 않았기 때문이다. 그러나 위 코드는 올바로 실행된다. 이것은 function이 동적으로 실행되는 명령이 아니라 정적인 구조를 선언하기 위한 키워드이기 때문이다. 다시 말해, function 명령은 코드를 해석/컴파일하는 타이밍에 함수를 등록한다

| 함수 리터럴/Function 생성자는 실행시에 판단된다.

			document.writeln(triangle(5,2));	//에러

			var triangle = function(base, height){
				return base * height / 2;
			}
		

반대로 위예에서 함수 리터럴, 또는 Function 생성자로 함수를 정의한 경우 실행시 에러가 난다. 즉, function 명령과 달리 함수 리터럴/Fuction 생성자는 실행 시(대입 시)에 판단된다. 따라서 함수 리터럴/Function 생성자로 함수를 정의하는 경우에는 호출원의 코드보다 먼저 기술해야 한다.

변수의 참조 - 스코프 -

스코프란 변수가 스크립트 안의 어느 장소에서 참조할 수 있는가를 결정하는 개념이다.

| 변수 선언에 var 명령은 필수

			scope = 'Global Variable';

			function getValue() {
			  scope = 'Local Variable';
			  return scope;
			}

			document.writeln(getValue());	//Local Variable
			document.writeln(scope);​		//Local Variable
		

변수를 정의한 장소에서 스코프가 결정된다. 하지만 위 예에서 변수 scope가 서로 다른 장소에서 정의가 되었지만 실행하면 모두 'Local Variable'이라는 동일한 값을 출력한다. 결론을 말하자면, Javascript에서는

var 명령을 사용하지 않고 선언된 변수는 모두 글로벌 변수로 본다.

따라서 변수를 정의하려면 반드시 var 명령을 사용해야 하며 이때 var 명령으로 정의된 변수는 정의한 장소에 따라 변수의 스코프가 정해진다라는 개념이 유효하다.

| 로컬 변수의 유효범위는 어디까지인가?

로컬 변수는 '선언된 함수 전체에서 유효한 변수'다. 하지만 var 명령으로 선언되기 전에 변수를 호출하게 되면 로컬 변수가 확보되기만 했을 뿐 내용은 미정의가 되어 'undefined'라는 값을 출력하게 된다. 그러므로 반드시 로컬변수는 함수의 선두에 선언하도록 한다.

			var scope = 'Global Variable';

			function getValue() {
				document.writeln(scope);		// undefined
				var scope = 'Local Variable';
				return scope;
			}

			document.writeln(getValue());		// Local Variable
			document.writeln(scope);			// Global Variable
		

| 가인수의 스코프 - 기본형과 참조형의 차이

가인수란 '호출원으로부터 함수에 대해 건네진 파라미터를 받는 변수'를 말하며 가인수는 기본적으로 로컬 변수로 처리된다.

			var value = 10;

			function decrementValue(value) {
			  value--;
			  return value;
			}

			document.writeln(decrementValue(100));	// 99
			document.writeln(value);				// 10
		

글로벌 변수 value와 로컬 변수(기본형) value는 별개의 것이므로, 로컬 변수에서의 변경이 글로벌 변수에 영향을 주지 않는다.

			var value = [1, 2, 4, 8, 16];

			function deleteElement(value) {
			  value.pop();
			  return value;
			}

			document.writeln(deleteElement(value));	// 1,2,4,8
			document.writeln(value);				// 1,2,4,8
		

정의된 글로벌 변수 value와 정의된 가인수(참조형 변수)가 변수로서는 별개이지만, 글로벌 변수 value의 값이 가인수 value에 전달되었을 시점에 참조하고 있는 메모리상의 영역의 주소를 건네주게 되어, 결과적으로 동일한 장소(주소)를 참조하게 된다.

| 블록 레벨의 스코프는 존재하지 않는다.

Javascript에서는 블록레벨의 스코프가 존재하지 않아 블록({...})에서 빠져나온 후에도 변수가 유효해 계속 사용할 수 있다.

			if(true){
				var i = 5;
			}
			document.writeln(i);	// 5
		

| 함수 리터럴/Function 생성자에 있어서의 스코프의 차이

			var scope = 'Global Variable';

			function checkScope() {
			  var scope = 'Local Variable';

			  var f_lit = function() { return scope; };	// Local Variable
			  document.writeln(f_lit());

			  var f_con = new Function('return scope;');	// Global Variable
			  document.writeln(f_con());
			}

			checkScope();
		

위 예에서 함수 리터럴 f_lit도, Function 생성자 f_con도 함수 내부에서 정의하고 있어 변수 scope은 로컬 변수를 참조할 것처럼 보이나, 사실 Function 생성자에서는 글로벌 변수를 참조하고 있다.

arguments 객체 - 파라미터 정보 관리하기 -

| Javascript는 파라미터의 수를 체크하지 않는다.

Javascript는 부여되는 인수의 수가 함수 측에서 요구하는 수와 다른 경우에도 이를 체크하지 않는다. 그리고 이러한 인수 정보를 관리하는 것이 arguments 객체다. arguments 객체는 함수 안에서만 이용할 수 있는 특별한 객체다.

아래는 arguments 객체를 이용하여 실제로 주어진 인수의 수와 요구하는 인수의 수를 비교해 서로 다른 경우에는 에러를 되돌려주는 예이다.

			function showMessage(value) {
			  if(arguments.length != 1){	//건네받은 인수의 수가 한 개가 아닌 경우에 예외를 던짐.
				throw new Error('인수의 수가 틀립니다.:' + arguments.length);
			  }
			  document.writeln(value);
			}

			try {
			  showMessage('야마다', '스즈키');
			} catch(e) {
			  window.alert(e.message);
			}
		
■ 인수의 디폴트 값 설정

Javascript에서는 모든 인수는 생략 가능 하지만 올바르게 동작하지 않을 수 있으므로 인수의 디폴트값을 설정해둘 필요가 있다.

			function triangle(base, height) {
				if (base == undefined) { base = 1; }
				if (height == undefined) { height = 1; }
				return base * height / 2;
			}

			document.writeln(triangle(5));
		

위 예에서는 인수의 내용을 체크해 undefined(미정의)였을 경우에는 각각 값을 세트하고 있다.

| 가변 인수의 함수 정의하기

가변 인수의 함수란 인수의 개수가 미리 정해져 있지 않은 함수를 말한다. 다시 말해, 선언시에 인수의 개수를 확정할 수 없는 함수이다.

			function sum() {
				var result = 0;
				//주어진 인수를 순서대로 취득하여 차례로 더하는 처리
				for (var i = 0; i < arguments.length; i++) {
					var tmp = arguments[i];
					if (isNaN(tmp)) {
						throw new Error('지정값이 숫자가 아닙니다.:' + tmp);
					}
					result += tmp;
				}
				return result;
			}

			try {
				document.writeln(sum(1, 3, 5, 7, 9));
			} catch(e) {
				window.alert(e.message);
			}
		

위 예는 인수에 주어진 수치의 합계를 구하는 sum 함수를 정의한 예다. arguments 객체로 부터 i번째 요소를 취득해 내려면 arguments[i]와 같이 기술한다. 또 글로벌 함수 isNaN은 취득한 요소값이 수치인지를 확인하는데 true를 돌려주는 경우에는 Error 객체를 호출원에 던져 처리를 중단한다.

| 재귀 호출 정의하기 - callee 프로퍼티 -

arguments 객체에서 또 하나 중요한 것이 현재 실행 중인 함수 자신을 참조하기 위해 준비된 callee 프로퍼티다. callee 프로퍼티를 이용하면 함수 등 특정 처리안에서 자기 자신을 호출하는 재귀 호출의 처리를 용이하게 기술할 수 있게 된다.

			function factorial(n) {
				if (n != 0) {
					return n * arguments.callee(n - 1);	
				return 1;
			}

			document.writeln(factorial(5));		// 120​
		

위 예에서 callee 프로퍼티를 사용하지 않고 아래와 같이 기술할 수도 있다.

			return n * factorial(n - 1);
		

하지만 원래의 함수명이 변경되었을 경우에 함수 본체의 기술도 수정해야 한다라는 점에서 그다지 바람직하지 않다.

높은 수준의 함수 테마

| 인수에 이름을 붙여 코드를 읽기 쉽게 하기

Named Function Parameters란 호출 시에 이름을 명시적으로 지정할 수 있는 인수를 말한다.

			triangle({base:5, height:4})​
		

아래는 Named Function Parameters의 구체적인 구현 방법이다.

			function triangle(args) {
				if (args.base == undefined) { args.base = 1; }
				if (args.height == undefined) { args.height = 1; }
				return args.base * args.height / 2;
			}

			document.writeln(triangle({ base:5, height:4 }));
		

결국 Named Function Parameters는 인수를 익명 객체(여기에서는 가인수 args)로 받고 있을 뿐이다. 메소드 안에서도 객체의 프로퍼티로서 각각의 인수에 액세스하고 있는 점에 주목해야 한다. 그리고 함수 호출에서 인수를 {...}라고 기술하고 있는 것은 익명 객체를 나타내고 있기 때문이다.

| 함수의 인수도 함수 - 고계 함수 -

함수 그 자체도 다른 수치형이나 문자열형 등과 같이 함수의 인수로서 인도하거나 반환값으로서 돌려주거나 할 수 있다. 이처럼 함수를 인수, 반환값으로서 취급하는 함수를 고계 함수라고 부른다.

다음 예에서 arrayWalk 함수는 인수로 주어진 배열 data의 내용을 지정된 사용자정의 함수 f의 규칙에 따라 차례대로 처리하기 위한 고계 함수다.

			//고계 함수 arrayWalk를 정의
			function arrayWalk(data, f) {
				for (var key in data) {
					f(key, data[key]);
				}
			}

			//배열을 처리하기 위한 사용자 정의 함수
			function showElement(key, value) {
				document.writeln(key + ':' + value);
			}

			var ary = [1, 2, 4, 8, 16];
			arrayWalk(ary, showElement);
		

다음은 배열 내의 요소를 순서대로 더하여 최종적으로 배열 내 요소의 합계를 구하기 위한 코드다.

			//고계 함수 arrayWalk를 정의
			function arrayWalk(data, f) {
				for (var key in data) {
					f(key, data[key]);
				}
			}

			var result= 0;	// 결과값을 대입하기 위한 글로벌 변수
			function sumElement(key, value) {
				result += value;	// 주어진 배열 요소로 변수 result를 가산
			}

			var ary = [1, 2, 4, 8, 16];
			arrayWalk(ary, sumElement);
			document.writeln('합계치:' + result);	// 31
		

위 예에서 베이스가 되는 arrayWalk 함수를 일체 고쳐 쓰지 않은 점에 주목해야 한다. 이와 같이 고계함수를 이용함으로써 큰 범위의 기능만을 정의해두고 상세한 기능은 함수의 이용자가 자유롭게 결정하는 것이 가능하다.

| 일회용 함수는 익명 함수로

고계함수에서 처럼 인수로서 주어지는 함수가 그 장소에 한해서만 사용되는 경우라면 이름을 부여한 함수로서 정의하는 것 보다는 익명함수로서 기술하는 편이 코드를 더 간결하게 만들어준다.

			//고계 함수 arrayWalk를 정의
			function arrayWalk(data, f) {
				for (var key in data) {
					f(key, data[key]);
				}
			}

			var ary = [1, 2, 4, 8, 16];
			arrayWalk(
				ary, 
				function (key, value) {
					document.writeln(key + ':' + value);
				}
			);
		

이러한 기법은 보다 고도의 스크립트(특히 Ajax의 콜백 함수)를 기술하는 데 중요한 기법으로 여겨진다.

| 스코프 체인

Javascript에서는 스크립트의 실행 시에 내부적으로 Global 객체를 생성한다. 글로벌객체는 글로벌 변수나 글로벌 함수를 관리하기 위한 편의적인 객체다. 글로벌 변수나 글로벌 함수는 글로벌 객체의 프로퍼티나 메소드라고 바꾸어 말할 수도 있다. 로컬 변수도 실은 Call 객체의 프로퍼티다.

Call 객체는 함수 호출이 있을 때마다 내부적으로 자동 생성되는 객체다. 글로벌 객체와 같이 함수 내에서 정의된 로컬변수를 관리하기 위한 편의적인 객체로, 실은 arguments 프로퍼티도 Call 객체의 프로퍼티다.

스코프 체인이란 글로벌 객체, Call 객체를 생성 순서대로 연결한 리스트를 말한다. Javascript에서는 이 스코프 체인의 선두에 위치하는 객체로부터 순서대로 프로퍼티(변수)를 검색해, 매치하는 프로퍼티가 처음 발견된 곳에서 그 값을 채택하고 있다. 다시 말해, 체인 선두(가장 안쪽의 함수)의 Call 객체부터 글로벌 객체까지 지정된 변수를 순서대로 검색한다. 스코프 체인을 이해함으로써 변수명이 중복되었을 경우에 변수의 해결에 대한 명확한 규칙을 알수 있다.

			var  y = 'Global';
			function outerFunc(){
			  var y = 'Local Outer';

			  function innerFunc(){
				var z = 'Local Inner';
				document.writeln(z);	// Local Inner
				document.writeln(y);	// Local Outer
				document.writeln(x);	// 에러(변수x는 미정의)
			  }

			  innerFunc();
			}

			outerFunc();
		

위 예에서 스코프 체인의 순서는 innerFunc 함수의 Call 객체 → outerFunc 함수의 Call 객체 → 글로벌 객체의 순으로 연결되며 가장 안쪽의 함수인 innerFunc 함수의 Call 객체로 부터 변수를 검색해 글로벌 객체까지 순서대로 검색하면서 처음 발견된 곳에서 그 값을 채택한다.

| 클로저

클로저란 로컬 변수를 참조하고 있는 함수 내의 함수를 말한다.

			function closure(init) {
			  var counter = init;

			  return function() {
				return ++counter;
			  }
			}

			var myClosure = closure(1);
			document.writeln(myClosure());	// 2
			document.writeln(myClosure());	// 3
			document.writeln(myClosure());	// 4
		

보통은 함수 안에서 사용된 로컬 변수(counter 변수)는 함수의 처리가 종료한 시점에서 파기가 된다. 그러나 위 예에서 closure 함수로부터 되돌려진 '익명 함수가 로컬 변수 counter를 계속 참조하고 있다'라는 이유로 closure 함수의 종료 후에도 로컬 변수 counter는 계속해서 보관/유지 된다.

바꾸어 말하면 익명함수를 나타내는 Call 객체 → closure 함수의 Call 객체 → 글로벌 객체라는 스코프 체인이 익명 함수가 유효한 사이에는 보관/유지된다라는 뜻이 된다.

위 예에서는 우선 처음의 closure 함수 호출로 변수 myClosure에 익명함수가 세트된다. 여기서 익명 함수 myCosure는 로컬 변수 counter를 유지하면서도 원래의 closure 함수와는 독립해 동작할 수 있게 된다. 결과적으로, 그 후에 myClosure 함수를 호출할 때마다 변수 counter는 증가되면서 2,3,4.... 라는 결과를 얻을 수 있게 된다.

정리하자면 'Call 객체는 함수가 호출될 때마다 생성된다'라고 말한 것처럼 각각의 스코프 체인은 독립된 것. 그리고 그 안에 관리되는 로컬 변수 counter도 또한 별개의 것이라는 사실이다.

■ 클로저는 간단한 객체

클로저는 간단한 객체라고 바꿔 말할 수 있다. 하지만 클로저와 객체는 완전히 동일한 것이 아니다. 우선, 클로저는 그 구조상 하나의 스코프에 대해 가질 수 있는 처리는 하나뿐이기 때문이다. 다음과 같이 구분할 수 있다.

객체지향 구문


Javascript의 객체지향 특징

| 클래스는 없고 프로토타입만 있다.

Javascript는 훌륭한 객체지향 언어다. 그러나 인스턴스화 및 인스턴스라는 개념은 존재하나, 이른바 클래스가 없고 프로토타입이라고 하는 개념만이 존재한다.

프로토타입이란 어떤 객체의 원본이 되는 객체로서 Javascrpt에서는 이것을 이용하여 새로운 객체를 생성해 나간다. 이러한 성질 때문에 Javascript의 객체지향은 프로토타입 베이스의 객체지향이라고 불린다.

| 간단한 클래스 정의하기

아래는 간단한 Javascript의 클래스를 정의한 예이다.

			var Member = function(){};
		

이 Member 클래스는 아래와 같이 new 연산자로 인스턴스화가 가능하다.

			var mem = new Member();
		

Javascript에서는 엄밀한 의미의 클래스라는 개념이 존재하지 않는다. 다만 Javascript에서는 함수(Function 객체)에 클래스로서의 역할을 부여하고 있다.

| 생성자로 초기화하기

new 연산자에 의해서 객체를 생성하는 것을 규정한 함수 객체를 생성자라고 말한다. 생성자라는 것은 '인스턴스(객체)를 생성할 때 객체의 초기화 처리를 기술하기 위한 특수한 메소드(함수)'를 말한다. 위 예에서 Member 함수 또한 new 연산자에 의해서 호출되어 객체를 생성한다는 의미로, 엄밀하게 클래스 그 자체라고 하기보다는 생성자라고 부르는 것이 보다 올바른 표현이다. 생성자의 이름은 보통 함수와 구별하기 위해서 대문자로 시작하는 것이 일반적이다.

			var Member = function(firstName, lastName){
				this.firstName = firstName;
				this.lastName = lastName;
				this.getName = function(){
					return this.lastName + '' + this.firstName;
				}
			};

			var mem = new Member('지성','박');
			document.writeln(mem.getName());	// 박 지성
		

여기서 this 키워드는 생성자에 의해서 생성되는 인스턴스(즉, 자기자신)를 나타낸다. this 키워드에 대해서 변수를 지정함으로써 인스턴스의 프로퍼티를 설정할 수 있다.

			this.프로퍼티명 = 값;
		

프로퍼티에는 문자열이나 정수, 날짜 등만이 아닌, 함수 객체(함수 리터럴)을 지정할 수 있는데 프로퍼티로 지정된 함수객체를 메소드라고 한다.

생성자에는 반환값이 불필요
생성자 함수는 반환값을 돌려주어서는 안된다. 어디까지나 생성자의 역할은 '지금부터 생성하는 객체를 초기화한다'라는 것이 목적이므로 반환값 자체는 불필요하다.
글로벌 변수/함수는 가능한 한 줄일것. 그러기 위해 관련된 기능이나 정보는 정적 멤버로 대체하는 것을 고려해야 한다.

| 동적으로 메소드 추가하기

메소드는 생성자에서만 정의할 수 있는 것이 아니다. new 연산자로 일단 인스턴스화해 버린 객체에 대해서 나중에 메소드를 추가할 수도 있다.

			var Member = function(firstName, lastName){
				this.firstName = firstName;
				this.lastName = lastName;
			};

			var mem = new Member('지성','박');
			mem.getName = function(){
				return this.lastName + '' + this.firstName;
			}
			

			document.writeln(mem.getName());	// 박 지성
		

위는 인스턴스에 대해서 직접 멤버(메소드나 프로퍼티)를 추가한 예이다. 주의할 점은 Member 클래스 그 자체가 아닌, '생성된 인스턴스에 대해서 메소드가 추가되고 있다'라는 점이다. 그래서 아래와 같이 mem2로 부터 동적으로 추가한 getName 메소드를 호출하려고 하면, '개체가 이 속성 또는 메소드를 지원하지 않습니다'라고 하는 메시지가 반환된다.

			var Member = function(firstName, lastName){
				this.firstName = firstName;
				this.lastName = lastName;
			};

			var mem = new Member('지성','박');
			mem.getName = function(){
				return this.lastName + '' + this.firstName;
			}
			
			document.writeln(mem.getName());	// 박 지성

			var mem2 = new Member('승엽','이');
			document.writeln(mem2.getName());	// 개체가 이 속성 또는 메소드를 지원하지 않습니다.
		

프로토타입 베이스의 객체지향의 세계에서는 동일한 클래스를 기초로 생성된 인스턴스라 할지라도 각각이 가지는 멤버가 동일하다라고 한정할 수 없다라는 점을 알아두어야 한다.

생성자의 문제점과 프로토타입

| 메소드는 프로토타입으로 선언한다 - prototype 프로퍼티 -

인스턴스 공통의 메소드를 정의하려면 적어도 생성자로 메소드를 정의할 필요가 있다. 그러나 실은 생성자에 의한 메소드 추가에는 '메소드의 수에 비례하여 쓸데없이 메모리를 소비한다'라는 큰 문제점이 있다. 생성자는 인스턴스를 생성할 때마다 각각의 인스턴스를 위해 메모리를 확보한다. 그러나 인스턴스 단위로 메모리를 확보하는 것은 쓸데없는 일이다.

Javascript에서는 객체에 멤버를 추가하기 위한 prototype이라고 하는 프로퍼티를 준비하고 있다. prototype 프로퍼티는 디폴트로 빈 객체를 참조하고 있지만, 이것에 프로퍼티나 메소드를 추가할 수 있다. 그리고 이 prototype 프로퍼티에 대입된 멤버는 인스턴스화된 앞의 객체에 인계된다. 다시 말해 prototype 프로퍼티에 대해 추가된 멤버는 그 클래스(생성자)를 기초로 생성된 모든 인스턴스로부터 이용할 수 있다는 것이다.

정의하자면 객체를 인스턴스화했을 경우 인스턴스는 베이스가 되는 객체에 속하는 prototype 객체에 대해서 암묵적인 참조를 갖게 된다.

			var Member = function(firstName, lastName){
				this.firstName = firstName;
				this.lastName = lastName;
			};

			Member.prototype.getName = function(){
				return this.lastName + '' + this.firstName;
			};

			var mem = new Member('지성','박');
			document.writeln(mem.getName());	// 박 지성

		

| 프로토타입 객체를 이용하는 두 가지 이점

프로토타입 객체(prototype 프로퍼티가 참조하는 객체)를 개입시켜 메소드를 정의하는 것에는 두가지 이점이 있다.

  1. 1) 메모리의 사용량을 절감할 수 있다.

    프로토타입 객체의 내용은 어디까지나 베이스가 되는 객체로부터 암묵적으로 참조만 될 뿐 인스턴스에 복사되는 것은 아니다.
    Javascript에서는 객체의 멤버가 호출되었을 때에

    • 인스턴스 측에 요구된 멤버가 존재하지 않는지를 확인
    • 존재하지 않는 경우는 암묵적인 참조를 통해 프로토타입 객체를 검색
  2. 2) 멤버의 추가나 변경을 인스턴스가 실시간으로 인식할 수 있다.

    인스턴스에 멤버를 복사하지 않는다라고 하는 것은 프로토타입 객체에의 변경을 인스턴스 측에서 동적으로 인식할 수 있다라는 뜻이 된다.

| 프로토타입 객체 - 프로퍼티의 설정 -

			var Member = function(){};
			Member.prototype.sex = '남자';

			var mem1 = new Member();
			var mem2 = new Member();
			document.writeln(mem1.sex + '|' + mem2.sex);	// 남자 | 남자

			mem2.sex = '여자';
			document.writeln(mem1.sex + '|' + mem2.sex);	// 남자 | 여자
		

프로토타입 객체(prototype 프로퍼티가 참조하는 객체)로 프로퍼티를 선언한 후 인스턴스를 생성하면 인스턴스는 프로퍼티를 암묵적으로 참조한다. 그러나 그 후 인스턴스에서 프로퍼티의 값을 변경(설정)하면 인스턴스자신이 프로퍼티를 가지게 됨으로써 인스턴스는 프로토타입을 참조할 필요가 없어진다. 이와 같이 프로퍼티를 프로토타입으로 정의해도 동작상으로는 '인스턴스가 개별적으로 프로퍼티를 보유하고 있는 것처럼 보이기'때문에 문제는 없다. 다만, 본래 인스턴스 단위로 값이 달라야 할 프로퍼티를 프로토타입 객체로 선언하는 것은 의미가 없다. 그러므로 보통 다음과 같이 구분하여 사용해야 한다.

■ 정적 프로퍼티/정적 메소드를 정의하려면

정적 프로퍼티/정적 메소드란 인스턴스를 생성하지 않아도 객체로부터 직접 호출할 수 있는 프로퍼티/메소드다. 정적 프로퍼티/정적 메소드를 정의하는 경우 프로토타입 객체에는 등록할 수 없기때문에 주의가 필요하다.(프로토타입 객체는 어디까지나 인스턴스로부터 암묵적으로 참조되는 것을 목적으로 한 객체이다) 정적 프로퍼티/정점 메소드는 다음과 같이 생성자에 직접 추가한다.

			객체명.프로퍼티명 = 값
			객체명.메소드명 = function(){...}
		
			var Area = function(){};	//생성자

			Area.version = '1.0';	//정적 프로퍼티 정의

			Area.triangle = function(base, height){		//정적 메소드 정의
				return base * height / 2;
			}

			Area.diamond = function(width, height){		//정적 메소드 정의
				return width * height / 2;
			}

			document.writeln('Area 클래스의 버전:' + Area.version);		//1.0
			document.writeln('삼각형의 면적:' + Area.triangle(5,3));		//7.5

			var a = new Area();
			document.writeln('마름모의 면적:' + a.diamond(10,2));		//에러
		

위 예에서 인스턴스 경유로 정적 메소드를 호출하려고 한 경우에 '객체가 이 속성 또는 메소드를 지원하지 않습니다'라는 에러가 발생한다. 정적 멤버는 어디까지나 Area라고 하는 함수 객체에 동적으로 추가된 멤버이지 Area가 생성하는 인스턴스에 추가된 것이 아니다.

■ 정적 프로퍼티/정적 메소드를 정의할 때의 두가지 주의점
  1. 1) 정적 프로퍼티는 기본적으로 읽기 전용

    정적 프로퍼티는 인스턴스 프로퍼티와는 달리 '클래스 단위로 보유되는 정보'다. 즉, 그 내용을 변경했을 경우는 스크립트 내의 모든 내용에 변경이 반영되어 버리게 된다. 원칙적으로 정적 프로퍼티로 정의한 값은 읽기 전용의 용도로 한정해야 한다.

  2. 2) 정적 메소드 안에서 this 키워드는 사용할 수 없다.

    정적 메소드 안에서이 this 키워드는 생성자(함수객체)를 나타낸다. 인스턴스가 없기 때문에 정적 메소드로부터 인스턴스 프로퍼티의 값에 액세스할 수 없다. 그러므로 정적 메소드 안에서는 this 키워드를 사용해도 의미가 없다.

왜 정적 멤버를 정의하는 것인가
정적 멤버는 기능적으로 글로벌 변수/함수와 서로 아무런 차이가 없다. 그러나 글로벌 변수/함수는 이름이 충돌하는 원인이 된다. 글로벌 변수/함수를 포함한 라이브러리가 있다면 여기서 정의된 글로벌 변수와 함수는 예약어가 된다. 즉, 글로벌 변수/함수가 많아지면 많아질수록 애플리케이션 측에서는 이름이 충돌할지 모른다는 것을 의식해야 한다. 이 때문에 글로벌 변수/함수는 가능한 한 적게 사용해야 한다. 정적 멤버를 이용하면 변수/함수는 클래스 밑에 속하게 되기 때문에 이러한 경합(충돌)의 가능성을 줄일 수 있다.

| 프로토타입 객체 - 프로퍼티의 삭제 -

			var Member = function(){};
			Member.prototype.sex = '남자';

			var mem1 = new Member();
			var mem2 = new Member();
			document.writeln(mem1.sex + '|' + mem2.sex);	// 남자 | 남자

			mem2.sex = '여자';
			document.writeln(mem1.sex + '|' + mem2.sex);	// 남자 | 여자

			delete mem1.sex;
			delete mem2.sex;
			document.writeln(mem1.sex + '|' + mem2.sex);	// 남자 | 남자
		

delete 연산자를 이용하여 프로퍼티를 삭제하려고 할 경우 프로토타입 객체의 프로퍼티를 암묵적으로 참조하는 인스턴스에는 프로퍼티가 존재하지 않으므로 delete 연산자는 아무것도 하지 않는다. 인스턴스에서 프로토타입 객체의 프로퍼티를 변경한 경우 인스턴스 자신이 프로퍼티를 가지게 되므로 delete 연산자는 인스턴스 상의 프로퍼티를 삭제한다. 그 결과 인스턴스는 다시 프로토타입 객체의 프로퍼티를 암묵적으로 참조하게된다. 다시 말해 삭제는 인스턴스 단위로만 행해진다.

| 객체 리터럴로 프로토타입 정의하기

닷연산자(.)를 사용하여 프로토타입에 멤버를 추가할 경우 Member.prototype~ 이라는 기술이 반복해서 등장하여 코드가 장황해질 뿐만 아니라 객체명에 변경이 있을 경우에는 전부 수정을 해야 하는 등 바람직한 작성 방법이라고 할 수 없다. 그러나 객체 리터럴을 이용하면

			var Member = function(firstName, lastName){
				this.firstName = firstName;
				this.lastName = lastName;
			};

			Member.prototype = {
				getName : function(){return this.lastName + '' + this.firstName;},
				toString : function(){return this.lastName + this.firstName;}
			};
		

보통 프로토타입을 정의하는 경우에는 위와 같이 리터럴 표현을 이용하는 것이 좋다.

객체의 계승 - 프로토타입 체인 -

| 계승이란?

계승이란 베이스가 되는 객체(클래스)의 기능을 계승하여 새로운 클래스를 정의하는 기능을 말한다.

계승에 있어서 계승원이 되는 클래스를 슈퍼 클래스(base class), 계승된 클래스를 서브 클래스(상속 클래스)라고 말한다.

| 프로토타입 체인의 기초

계승의 구조를 Javascript의 세계에서 실현하고 있는 것이 프로토타입 체인이다.

			var Animal = function(){};

			Animal.prototype = {
				walk : function(){document.writeln('종종...');}
			};

			var Dog = function(){};
			
			Dog.prototype = new Animal();

			Dog.prototype.bark = function(){document.write('멍멍!');};

			var d = new Dog();

			d.walk();	//종종...
			d.bark();	//멍멍!
		

위 예에서 Dog 객체의 프로토타입(Dog.prototype)이 Animal 객체의 인스턴스로 세트되고 있다. 이로 인해 Dog 객체의 인스턴스로부터 Animal 객체로 정의된 walk 메소드를 호출할수 있다.

  1. 1. Dog 객체의 인스턴스 d로 부터 멤버의 유무를 검색한다.
  2. 2. 해당하는 멤버가 존재하지 않는 경우에는 Dog 객체의 프로토타입 - 즉, Animal 객체의 인스턴스를 검색한다.
  3. 3. 거기서도 목적의 멤버가 발견되지 않는 경우에는 바로 위의 Animal 객체의 프로토타입을 검색한다.

Javascript에서는 프로토타입에 인스턴스를 설정함으로써 인스턴스끼리 암묵의 참조하에 서로 연결되어 계승 관계를 갖게 할 수 있다. 그리고 이러한 프로토타입의 연결을 프로토타입 체인이라고 부른다.

프로토타입 체인의 종단은 Object.prototype
모든 객체의 루트가 되는 것은 Object 객체다. 즉, 모든 객체는 암묵적으로 Object 객체를 계승해, Object.prototype을 참조하고 있다. 바꾸어 말하면, 프로토타입 체인의 종단에는 반드시 Object.prototype이 있다.

| 계승 관계는 동적으로 변경 가능

Javascript에 의한 계승은 동적이다. 즉, 이 말은 동일한 객체가 어느 타이밍에서는 객체 X를 계승하다가도 이후의 다른 타이밍에서 객체 Y를 계승할 수도 있다는 뜻이다.

			var Animal = function(){};
			Animal.prototype = {
				walk : function(){document.writeln('종종...');}
			};

			var SuperAnimal = function(){};
			SuperAnimal.prototype = {
				walk : function(){document.writeln('다다다닷!');}
			};

			var Dog = function(){};
			Dog.prototype = new Animal();	//Animal 객체를 계승
			var d1 = new Dog();
			d1.walk();	//종종...

			Dog.prototype = new SuperAnimal();	//SuperAnimal 객체를 계승
			var d2 = new Dog();
			d2.walk();	//다다다닷!
			d1.wlak();	//종종...
		

그러나 Javascript의 프로토타입 체인은 인스턴스가 생성된 시점에서 고정되어 그 후의 변경에는 관여치 않고 보존된다.

클래스와 비슷한 계승의 구조

아래는 Javascript로 클래스 기반의 객체지향과 매우 비슷한 방식의 계승을 구현한 예이다.

			function initializeBase(derive, base, baseArgs){
				base.apply(derive, baseArgs);
				for(prop in base.prototype){
					var proto = derive.constructor.prototype;
					if(!proto[prop]){
						proto[prop] = base.prototype[prop];
					}
				}
			}
			
			//Member 클래스를 정의
			var Member = function(firstName, lastName){
				this.firstName = firstName;
				this.lastName = lastName;
			};

			Member.prototype.getName = function(){
				return this.lastName + '' + this.firstName;
			};

			//Member클래스를 계승한 SpecialMember 클래스를 정의
			var SpecialMember = function(firstName, lastName, role){
				initializeBase(this, Member, [firstName, lastName]);
				this.role = role;
			}

			SpecialMember.prototype.isAdministrator = function(){
				return (this.role == 'Administrator');
			};

			var mem = new SpecialMember('지성', '박', 'Administrator');
			document.writeln('이름: ' + mem.getName());				//박지성
			document.writlen('관리자: ' + mem.isAdministrator());	//true

		

위 예에서 주목해야 할 부분은 initializeBase 함수다. initializeBase 함수는 상속 클래스의 생성자 안에서 호출되며 아래와 같은 역할을 제공한다.

initializeBase 함수를 호출하는 구문은 다음과 같다.

			initializeBase(현재의 인스턴스, 부모 클래스, 인수 배열)
		

슈퍼 클래스의 생성자를 호출함과 동시에 현재의 클래스에 대해 부모 클래스에서 정의한 멤버를 복사한다. '인수 배열'은 부모 클래스의 생성자에 건네주는 인수다.

initializeBase 함수에서 슈퍼 클래스의 생성자를 호출하는 부분은 다음과 같다.

			base.apply(derive, baseArgs);
		

apply 메소드는 Function 객체가 제공하는 메소드중의 하나로, 함수를 마치 지정된 객체(여기에서는 인수 derive)의 메소드인 것처럼 호출할 수 있다. 여기에서는 인수 derive에 상속 클래스의 생성자로 부터 this 키워드가 건네지고 있다. 즉, 상속 클래스(인스턴스)의 메소드로서 부모 클래스의 생성자(Member 함수)를 호출하고 있는 것이다. 메소드(생성자) 호출에 필요한 인수는 apply 메소드의 제 2인수로, 배열 레터럴로 지정한다. 이로서 부모클래스의 멤버를 상속 클래스로 복사를 한다.

그 다음, 슈퍼 클래스에서 정의된 프로토타입 멤버를 상속 클래스에서도 사용할 수 있도록 복사하는 부분은 다음과 같다.

			for(prop in base.prototype){
				var proto = derive.constructor.prototype;
				if(!proto[prop]){
					proto[prop] = base.prototype[prop];
				}
			}
		

슈퍼 클래스에서 이용 가능한 멤버(메소드)는 프로토타입 객체(prototype 프로퍼티)밑에 등록되어 있을 것이다. 그래서 여기에서는 이 프로토타입 객체의 내용을 for...in 루프를 이용하여 모두 꺼내 그대로 상속 클래스의 프로토타입 객체에 세트하고 있다. 다만, 상속 클래스에서 동일 이름의 메소드가 이미 정의되어 있는 경우를 고려해, 동일 이름의 메소드가 존재하지 않는 경우에만 복사 처리를 행하도록 한다. 또한 여기서 대인하려는 곳이 인스턴스의 프로토타입 객체가 아니라 생성자 함수(여기에서는 SpeicalMember)의 프로토타입 객체인 점도 주의해야 한다. 인수 derive에는 상속 클래스의 인스턴스가 세트되어 있을 것이므로 여기에서는 constructor 프로퍼티로 생성자를 취득하고 있다.

| 슈퍼 클래스의 메소드를 상속 클래스로 이용하려면

슈퍼 클래스로 정의된 메소드를 유지하면서 상속 클래스의 독자적인 기능을 메소드에 추가하고 싶은 경우에는 '상속 클래스의 메소드로부터 슈퍼 클래스의 메소드를 호출'하는 행위가 필요하다. 예를 들면, 다음은 Member 클래스에서 정의된 getName 메소드를 덮어쓰고, 이름의 말미에 롤(role) 정보를 추가하는 예이다.

			//Member클래스를 계승한 SpecialMember 클래스를 정의
			var SpecialMember = function(firstName, lastName, role){
				initializeBase(this, Member, [firstName, lastName]);
				this.role = role;
			}

			SpecialMember.prototype.getName = function(){
				var result = Member.prototype[getName].apply(this, []);
				return result + '(' + this.role + ')';
			};

			var mem = SpecialMember('지성', '박', 'Administrator');
			document.writeln('이름: ' + mem.getName());				//이름:박지성(Administrator)

		

슈퍼 클래스에서 정의된 메소드는 apply 메소드로 호출한다. 여기에서는 Member.getName 메소드로 얻은 결과를 일단 변수 result에 세트한 다음, 이 것을 가공한 것을 새로운 SepecialMember.getName 메소드의 반환값으로 되돌리고 있다.

private 멤버 정의하기

private 멤버란 클래스 내부의 메소드에서만 호출할 수 있는 프로퍼티/메소드를 말한다. 클래스 내외로부터 자유롭게 액세스할 수 있는 멤버를 public 멤버라 하며 아무런 고려없이 멤버를 정의하면, public 멤버가 된다.

			function Triangle(){
				//private 프로퍼티의 정의
				var _base;
				var _height;

				//private 메소드의 정의
				var _checkArgs = function(val){
					return (!isNaN(val) && val > 0);
				}

				//private 멤버에 액세스하기 위한 메소드의 정의
				this.setBase = function(base){
					if(_checkArgs(base)){_base = base;}
				}
				this.setHeight = function(base){
					if(_checkArgs(base)){_height = height;}
				}
				this.getBase = function(){return _base;}
				this.getHeight = function(){return _height;}
			}

			//private 멤버에 액세스하지 않는 보통의 메소드를 정의
			Triangle.prototype.getArea = function(){
				return this.getBase() * this.getHeight()/2;
			}

			var t = new Triangle();
			t._base = 10;
			t._height = 2;
			document.writeln('삼각형의 면적:' + t.getArea());	// NaN

			t.setBase(10);
			t.setHeight(2);
			document.writeln('삼각형의 밑변:' + t.getBase());	// 10
			document.writeln('삼각형의 높이:' + t.getHeight());	// 2
			document.writeln('삼각형의 면적:' + t.getArea());	// 10

		
  1. 1) private 멤버는 생성자 함수에서 정의한다.

    private 멤버는 다음과 같이 생성자 함수안에서 정의해야 한다.

    					var 프로퍼티명
    					var 메소드명 =function([인수]){}
    				

    private 멤버를 정의하는 경우에는 this.프로퍼티명, this.메소드명=~ 과 같이 this 키워드가 아니고 var 키워드로 선언한다는 점에 주목해야 한다. 위 예에서 _base, _height, _checkArgs 메소드가 private 멤버가 된다.

  2. 2) privileged 메소드를 정의해 private 멤버에 액세스하기

    private 멤버에 액세스할 수 있는 메소드를 privileged 메소드라 한다. privileged 메소드는 생성자함수 안에서 정의한다.

    privileged 메소드는 생성자 안에서 내부 정의된 함수. 즉, 클로저다. 그 때문에 생성자 함수안에서 정의된 private 멤버(로컬 변수)에도 자유롭게 액세스할 수 있다.

    privileged 멤버에는 public 멤버나 클래스 외부로부터 자유롭게 액세스할수 있다.

| 액세스메서드 경유로 프로퍼티를 공개하기

프로퍼티 그 자체는 클래스 외부로부터 액세스할 수 없게 해두고, 대신에 프로퍼티에 액세스하기 위한 메소드를 준비하는 케이스는 실제 구현에 있어 자주 있는 코딩 방식이다. 이러한 메소드를 액세서메소드라고 한다. (참조용의 메소드를 GetterMethod, 설정용의 메소드를 SetterMethod라고 한다.) 액세서메소드 경유로 프로퍼티를 공개하는 경우 다음과 같은 이점이 있다.

엑세서메소드를 도입함으로써 프로퍼티를 보다 안전하게 조작할 수 있게 된다. 액세서메소드는 일반적으로 get<프로퍼티명>, set<프로퍼티명>의 형식으로 명명한다. 그리고 프로퍼티명의 머리글자는 대문자로 한다.

네임스페이스/패키지 작성하기

어느 정도 규모가 있는 클래스 라이브러리를 작성하는 경우에는 네임스페이스를 작성하여 클래스명이 충돌하는 것을 방지해야 한다.

Javascript에서는 빈 객체를 이용해 유사적으로 네임스페이스와 같은 것을 작성한다.

			var Wings = function(){};

			Wings.Member = function(firstName, lastName){
				this.firstName = firstName;
				this.lastName = lastName;
			};

			Wings.Member.prototype = {
				getName : function(){
					return this.lastName + '' + this.firstName;
				}
			};

			var mem = new Wings.Member('지성','박');
			document.write(mem.getName());	// 박지성
		

네임스페이스를 정의하려 한다면 단순히 '빈 생성자 함수를 생성'하기만 하면 된다. 위 예에서는 Wings 네임스페이스를 정의하고 있다.

DOM


특정 노드 취득하기

| ID 값을 키값으로 노드 취득하기 - getElementById 메서드 -

			document.getElementById('ID 값');
		

| 태그명을 키값으로 노드 취득하기 - getElementsByTagName 메서드 -

			document.getElementsByTagName('태그명');
		

getElementsByTagName 메서드는 복수의 요소가 서로 일치할 가능성이 있기 때문에 요소의 집합(NodeList 객체)을 반환할 수 있다.

NodeList 객체로부터 이용할 수 있는 멤버는 다음과 같다.

| class 속성을 키값으로 노드 취득하기 - getElementsByClassName 메서드 -

			document.getElementsByClassName('class명');
		

IE8 은 지원하지 않는다.

| 상대적인 위치 관계로 노드 취득하기

어떤 노드를 기점으로 해서 상대적인 위치 관계로부터 노드를 취득하는 것도 가능하다.

특정 요소밑에 있는 모든 자식 노드를 취득하려면 childNodes 프로퍼티를 이용할 수 있다. childNodes 프로퍼티는 NodeList 객체로서 반환하긴 하지만, 리스트에 포함된 노드는 '요소만이 아니다'라는 점에 주의가 필요하다. 태그사이에 있는 개행이나 공백도 포함될 수 있으며 이들은 텍스트 노드로 취급된다. 그러므로 취득한 노드가 요소 노드인지를 확인할 필요가 있는데 노드의 종류를 판별하는 것이 nodeType 프로퍼티의 역할이다.

nodeType 프로퍼티의 반환값
반환값 개요
1 요소 노드
2 속성 노드
3 텍스트 노드
4 CDATA 섹션
5 실제 참조 노드
6 실제 선언 노드
nodeType 프로퍼티의 반환값
반환값 개요
7 처리 명령 노드
8 코멘트 노드
9 문서 노드
10 문서형 선언 노드
11 문서의 단편
12 기법 선언 노드

다음 예제는 select 태그 밑에 있는 option 태그를 취득하여 그 값을 콤마(,)로 연결된 배열을 생성하는 코드다.

			

			...

			
가장 먹고 싶은 음식은?

아래와 같이 고쳐 쓸 수도 있다.

			

			...

			
가장 먹고 싶은 음식은?