by-nc-sa     개발자, DBA가 함께 만들어가는 구루비 지식창고!

객체지향적으로 자바스크립트 사용하기




객체지향적으로 자바스크립트 사용하기

1. 자바스크립트에서 OOP를 가능하게 하는 요소 (코어자바스크립트)

반드시 선행되어야할 학습 내용 : 기본학습

1.1 무엇이 자바스크립트를 OOP 코드로 구현할수 있게 하는가

위의 선행학습을 살펴 보았다는 (코드를 한번씩 파이어 폭스 브라우저 에서 테스트 해보았다) 가정하에 진행 합니다. 안 읽어 보셨다면
꼭 읽어 보세요. 살 까지는 몰라도 뼈는 될만한 내용이라 생각합니다.

OOP란 다들 알다 싶이 코드의 극단적 재활용을 추구하면서 나타난 개념입니다.
인간의 기본적인 사고 흐름(절차적) 과 다른 흐름(이벤트 중심이라 해야 하나? ) 을 가지고 있어서 초보 개발자는 OOP로 개발 하기가 쉽지가 않습니다.
대표적인 경우가 OOP언어이 자바와 C#을 가지고 C처럼 프로그램 하는 경우죠(main에 프로그램을 다 박아 넣는다던지.. 하는경우)
자바 스크립트는 OOP언어 임에도 불구 하고 그 태생과는 별계로 그동안 이벤트 중심으로 처리될 HTML 화면에서 단순히 에러체크 로직 정도의 역활만 하는 언어였습니다.
컴퓨팅 환경의 발전과 웹의 발전에 따라 시간이 지날수록 자바스크립 또한 점점 복잡해 졌고.. 그에따라 재사용성을 고민하던 여러 선배 개발자( 대부분이 외국인 입니다..ㅜ.ㅜ )
의 고민 덕분에 자바스크립트를 이렇게 써야 한다고 가이드 라인 까지 나오게 되었습니다.
그 가이드 라인의 핵심이 되는게 바로 prototype 프로퍼티 입니다.

prototype 는 OOP언어의 핵심인 상속.. 즉 재사용성을 멋들어 지게 구현할수 있게 도와주는 개발자 여러분( 바로 당신 ) 의 소중한 친구가 될것입니다.

다른 언어에서 OOP를 배운 분들을 위해 스크립트에서의 객체가 가진 차이점을 조금 설명 드리고 넘어 갈까 합니다. 알고 계시더라도 한번 읽고 넘어가 주세요.
일단 객체는 클래스로부터 값을 받아서 나왔으니 이름붙은 값들의 모임 입니다. 동의 하십니까?
보통 이 이름 붙은 값을 프로퍼티라고 부르고 객체의 프로퍼티에 접근 하기 위해서 객체명.프로퍼티명 이렇게 씁니다.
이프로퍼티에 함수가 저장 될때 그걸 메소드라고 할수 있습니다.
여기까지는 쉽게 이해 하실수 있을겁니다. 많이 보아왔던 형시과 다를게 별로 없고 또 충분히 이해 할수 있는 범위 니까요. 문제는
자바스크립트에서는 객체가 연관배열 의 역활도 수행을 합니다. 이게 정말 재미있고 독특한 역활을 하는데. 임의의 문자열에 임의의 값을 연결하는 역활을 합니다.
그리고!!! 자바스크립트에서는 함수명 즉 function 네임 또한 객체 입니다. Function 의 객체이지요.
(예제는 잠시후에 보여 드리겠습니다.)
객체를 생성하는 방법은 여타 다른 OOP언어와 다르지 않습니다. 충분히 이해할수 있는 범주 지요. 다음과 같습니다.

var o   =  new Object();
var now =  new Date();

이처럼 new 키워드를 이용면 됩니다.

자바스크립트에서는 객체 리터럴 이라고 하는 문법을 제공하고 있습니다. 이는 이름 / 값 쌍들이 쉼표로 구분 되어 관리는 형태 입니다.
아래 코드와 같이 생성할수 있습니다.

var point = {x:2.3 , y:-1.2}
//다음과 같이 객체 리터럴은 중첩되어서 표현도 가능 합니다.
var rectangle = { upperLeft: {x : 2, y: 2} ,
                  lowerRight : {x : 4, y: 4}
                };
var square    = { "upperLeft" : {x: point.x, y: point.y}, 
                  "lowerRight : {x: (point.x + side), y: (point.y + side )},
                };

var	ADSAFE = function () {	
		Event.observe(window, 'load', function() {
			$('eventJoin1').observe('click', Show.eventAct);
			$('eventJoin2').observe('click', Show.eventAct);
			$('eventJoin3').observe('click', Show.eventAct);
			$('eventJoin4').observe('click', Show.eventAct);
			$('eventJoin5').observe('click', Show.eventAct);
			$('eventJoin6').observe('click', Show.eventAct);
			$('eventJoin7').observe('click', Show.eventAct);
			$('eventJoin8').observe('click', Show.eventAct);
			$('eventJoin9').observe('click', Show.eventAct);
		});
	
		var Show = {
		    chkLoginVal	: "",
		    todayJoinCount	: "",
		    totalJoinCount	: "",
											    				    
		    eventAct: function(event) {
				if(!Show.chkLoginVal){
					alert("먼저 로그인을 하시고 참여해 주세요");
					window.open("xxxx.jsp", "gogosing", "");		
				}else{							
					if(Show.todayJoinCount > 0) {
						alert("오늘은 이미 참여 하였습니다.\n내일 다시 참여하시기 바랍니다.");
					}else{
						alert($F('tmp_1'));
					}
				}	    
		    }	    
		}
	}();

(마지막 예제는 프로토 타입 을 가져다가 쓴 샘플입니다.)
자 이 객체 리터럴 이라는 문법 체계를 보고 고개를 갸우뚱 할수도 있습니다. 머하러 저렇게 쓰냐 어리버리해 보인다. 어설프게 클래스 처럼 보이게 하려 한다.
라고 생각할수도 있는데.... 왜 이걸 가져다가 쓰냐면 단순한 이유로는 첫째가 어설프지만 그동안 봐온 OOP언어의 클래스 형태 처럼 꾸밀수 있어
직관적인게 가장 단순한 이유고 진짜 이유는 바로 JSON 인코딩 을 위해서 입니다.
(JSON 이란 데이터를 자바 스크립트 객체와 배열 리터럴로 인코딩 하는 방법을 말합니다 만... 자바스크립트를 전문적으로 하실분이 아니면 그냥 사용방법만 알면 됩니다)

객체를 알기 위해서 여러가지 이야기를 꺼내다가 조금 범위를 넘어선거 같지만서도 자바스크립트에서 객체와 배열은 따로 때어 놓을수 없는 관계로
자바스크립트에서의 배열도 같이 살펴보고 넘어가도록 하겠습니다.

아까 연관배열 이라는 이야기를 잠깐 꺼냈는데.. 연관 배열은 일반적으로 우리고 알고 있는 배열과는 조금 다름니다. 지금 이야기 하려하는 배열(일반적으로
우리가 알고 있는 배열)은 기본적으로 음수가 아닌 정수로 인덱싱을 할수 있는반명 연관 배열은 문자열로 인덱싱을 합니다 ( _; 이건 멍뮈...)

배열은 너무 뻔하니까 연관배열의 단순한 예제를 한번 살펴 보도록 하지요?(배열도 하나만 예제로 넣구요)

 var test1 = document.myform.btn1.value;
 var test2 = document.myform.btn2.value;
 var test3 = document.myform.btn3.value;
 
 var test4 = document.myform["btn1"].value;
 var test5 = document.myform["btn2"].value;
 var test6 = document.myform["btn3"].value;
 
 var a = new Array();
 a[0] = 1.2;
 a[1] = "javascript";
 a[2] = true;
 a[3] = {x : 1, y : 3 }

위코드는 연관배열을 보여주는 단순한 예제 입니다. 별로 어렵지 않지요? 그냥 보고 아 이런게 연관배열이구나 라고 이해 하고 넘어 가실수 있습니다.
아까 객체를 설명 할때 객체리터럴 이 있다고 했는데 그렇다면 배열리터럴 도 있을까요?
예 있습니다.
배열리터럴을 마지막으로 간단한 객체 설명을 마치고 실제 코딩으로 들어가 보도로 하겠습니다.

var a      = [1.2, "javascript", "true", {x:1, y:2}];
var matrix = [[1,2,3], [1.2], [7,8,9]]

1.2 prototype를 이용한 간단한 OOP코드의 구현

실은 이부분을 설명 하기에 앞서서 여러가지 이야기를 나누어야 합니다. call() 함수, apply 함수, 함수의 유효 범위, 클로저, 중첩함수등
그러나 이걸 이야기 하기에는 지면도 부족하고 무엇보다 현재 제가 할당 받은 부분을 넘어서는 관계로... 관감 하게 끊고 그냥 개발할대 자주 쓰는
그런 코드를 OOP적으로 생산 하는 방법을 살펴 보도록 하겠습니다.
우리는 기본적으로 개발을 할때 가장 자주 쓰는 코드가 무엇입니까??
그건 바로 Utill 클래스와 String 관련 클래스 입니다. 이 2가지 범용 클래스로 대부분의 개발을 진행할수 있습니다. 나머지야머... 프레임워크가 알아서 해주니까요.
그럼 저 2가지 클래스를 자바스크립트로 어떻게 개발해 나가는지 간단히 살펴 보겠습니다. ( 살은 본인 스스로 붙이도록 하세요
저는 그냥 prototype.js 나 jQuery 가져다가 사용합니다 히히)

function Something(p1, p2, p3) {
 this.Param1 = p1;
 this.Param2 = p2;
 this.Param3 = p3;
}

Something(1, 2, 3);      // 1
var valueObj = new Something(1, 2, 3);  // 2

자바스크립트에서 위 를 Something 실행하는 방법은 위에 적어 놓은거와 같이 2가지가 있습니다.
2개의 차이점은 무엇일까요? 첫번째 방법은 함수가 동작되었을때
일반적으로 웹브라우저에서는 전달받은 매개변수들을 현재 컨텍스트 객체 (아무것도 지정이 안되있기에 window 객체를 사용) 객체의 프로퍼티로
저장 합니다.. 하지만 2번째 방법으로 접근 하면 상황이 달라지지요..
2번째 방법은 지정된 함수를 OOP언어에서의 생성자로 바라보고 있습니다. *new 연산자는 새롭고 빈 Object 인스턴스를 생성한 다음에 함수를 호출하고,
이 함수에 피연산자는 함수 호출 컨텍스트로서 새로 생성된 객체가 됩니다. 이로인해 함수가 실행될때 this 포인터가 새로운 객체를 참조 하고,
함수는 객체에 대한 생성자가 됩니다. *

이러므로 valueObj 객체는 p1, p2, p3 값을 가지고 있는 Param1 ,Param2, Param3 3개의 프로퍼티를 가지게 됩니다. 즉 캡슐화를 손에 넣었군요.
이제 이 메커니즘을 이용해 좀더 확장해 나가 도록 하겠습니다.

//Book 생성자 version 0.1
function Book(title, artist, isbn) {
 this.title    = title;
 this.artist   = artist;
 this.isbn     = isbn;
}

한참전에 설명한 내용을 뒤져보면 *어떤 함수가 어떤 객체의 메서드가 되기 위해서는 해당 객체의 프로퍼티를 통해서 참조되어야 하며
이는 호출될때 함수 컨텍스트가 해당 객체(호출한 객체)의 인스턴스가 되도록 하기 위해서다 *
라고 위에 어딘가에 써놓았습니다.
그걸 이용해서 메소드를 객체에 메소드를 달아 보겠습니다.

var muder1 = new Book('rambo Destroy EveryThing', 'sucker', '000001')
muder1.Info = function() {
 alert( 'This Book info: ' + this.title + this.artist + this.isbn);
}

간단한 Book 생성자를 이용해서 영화사에 길이 남을 muder1 시리즈중 첫번?인 람보를 작성했습니다. 그런데 문제는 시리즈인 만큼 여러개가 필요하지요.
그럼 다음과 같이 되겠네요. 여러객체를 이용하기위해서 배열을 추가 했습니다.

var muders = new Array();

var muder1 = new Book('rambo Destroy EveryThing', 'sucker', '000001')
muder1.Info = function() {
 alert( 'This Book info: ' + this.title + this.artist + this.isbn);
}
muders.push( muder1 );
var muder2 = new Book('comando Destroy EveryThing', 'XXXX', '000002')
muder2.Info = function() {
 alert( 'This Book info: ' + this.title + this.artist + this.isbn);
}
muders.push( muder2 );

.
.
.

시리즈가 늘어 나면 늘어날수록 엄청난 행위를 해야 합니다. 이를 개선 하기 위해 Book 생성자를 수정해 보겠습니다.

//Book 생성자 version 0.2
function Book(title, artist, isbn) {
 this.title    = title;
 this.artist   = artist;
 this.isbn     = isbn;
 this.Info     = function() {
  alert( 'This Book info: ' + this.title + this.artist + this.isbn);
 };
}

var muder1 = new Book('rambo Destroy EveryThing', 'sucker', '000001')
muders.push( muder1 );
var muder2 = new Book('comando Destroy EveryThing', 'XXXX', '000002')
muders.push( muder2 );
.
.

음 훨씬 심플해 졌군요. 이렇게 처리 하므로 메서드 생성을 캡슐화 하는 목표를 달성했습니다. 하지만 문제가 있지요.
어떤 문제가 있을까요? version 0.2 코드를 가지고 생성한 모든 객체가 info 함수의 복사본을 소유하므로 메모리의 낭비가
발행하는 단점이 생깁니다. 정말 그런지 확인 하고 싶지요? IE에서는 힘즐지만(요즘은 IE에도 좋은 디버그 툴이 있으니 그걸로 보세요 )
FF프라우저를 이용해서 파이어 버그 플러그인을 설치 하시면 메모리 스택에 어떤구조로 값이 들어가는지 확인 할수있습니다.
더 자세한 설명은 기본학습 을 참고 하시면 저런 version 0.2의 방법이
어떤문제를 가지고 있고 어떻게 메모리상에 구조가 잡히는지 기본학습을 참조해 주세요.

다시 본론으로 돌아와서 prototype 프로퍼티를 이용한 좀더 영리한 처리 방법을 살펴 보도록 하겠습니다.
그리고 가격을 알아 보는 메소드를 추가로 넣어 보도록 하지요.

//Book 생성자 version 0.3
function Book(title, artist, isbn, price) {
 this.title    = title;
 this.artist   = artist;
 this.isbn     = isbn;
 this.price    = price;
}

Book.prototype = { 
                  Info : function() {
                                  alert( 'This Book info: ' + this.title + this.artist + this.isbn);
                         },
                  getPrice : function() {
                                  alert( 'This Book price is ' + this.price);
                         }
}

var muder1 = new Book('rambo Destroy EveryThing', 'sucker', '000001')
muders.push( muder1 );
var muder2 = new Book('comando Destroy EveryThing', 'XXXX', '000002')
muders.push( muder2 );
.
.

자이제 이걸 응용해서 자바스크립트의 객체에는 없는 trim 메소드를 작성해서 모든 객체가 이를 가질수 있게 작성해 보지요.

String.prototype = {
      trim : function() {
        return this.replace(/^\s+/, '').replace(/\s+$/, '');
      }
}

하하하.. 구현 코드는 Prototype.js 에서 들고 왔습니다. 머 이해해 주세요
이렇게 해서 간단 하게 우리가 정의한 객체의 prototype
를 응용하는 방법 그리고 코어스크립트(String 객체)의 prototype를 확장하는 방법 , JSON 을 사용하는 방법까지 개략적으로 한번 살펴 보았습니다.

여기 까지 크게 나는 문제 없이 이해 했다!!! 라고 하시는 분들은 이제 다음 내용을 살펴볼 자격을 얻게 되었습니다. 자 살펴 보러 갑시다.

이제 까지 글을 읽어 보면 의아하게 생각하시는 분들이 있을겁니다. 어떤부분이 의아 한가?
바로 생성자도 만들고 캡슐화 시키는 것도 알겠는데... 그럼 상속은 어떻게???

그렇습니다. 지금까지 우리는 상속에 대해서 저언혀 언급하지 않았습니다. 어떤코드에도 상속은 나타나 있지 않습니다.
이제 부터 그걸 알아 보려 합니다.

일반적으로 Prototype.js 와 같은 여타 프레임워크에서는 Object 와 같은 코어의 prototype를 바로 확장해서 사용하지않습니다. 왜!!!!
왜냐하면 당연히 좋지 않은 방법으로 치부되니까 입니다. 머가 좋지 않은지 그걸 이해 해햐 하겠지요? 이해 하러 가봅시다.

자바를 사용하면서 for in 문을 자주 사용하십니까?
음 저는 개인적으로 잘쓰지 않다가 몇달전부터 의식적으로 배열처리된 값을 처리 할때 부터 사용하기 시작했는데요.

굉장히 유용한 문장 입니다. 특히나 자바스크립트에서는 말이죠.
자바 스크립트에서는 객체의 prototype이나 프로퍼티 들이 배열 처럼 처리 됩니다.( 연관 배열 ) 이들을 이용할? for in 문이 많이 사용되지요.
예를 들면 다음 메소드 처럼요

// 한 클래스의 메서드를 다른 클래스에서 사용하기 위해 빌려온다.
// 전달인자는  클래스들의 생성자 함수가 되어야 한다.
// Object 와 Array, Date, RegExp 같은 내장 타입의 메서드는 열거 가능 하지 않고
// 아래 메소드를 통해서 빌려올수 없다.

function borrowMethods(borrowFrom, addTo) {
  var from = borrowFrom.prototype;
  var to   = addTo.prototype;
 
   for( m in from )  {
    if( typeof from[m] != "function" ) continue;
     to[m] = from[m];
   }
 }

위 메소드의 for in 형태는 엄청 자주 쓰이는 형태입니다. 특정 객체의 모든 prototype를 뒤지는 형태는 자바스크립트를 쓰다보면 거의 외우다 싶이 하게될
코드 입니다. 바로 이 for in 문장이 Object 의 prototype속성을 확장하는것에 좋지않은 영향을 끼치는 데요.

예를 들면 개발자 본인이 XX한 이유로 모든 객체가 생성된 시기를 알고 싶다고 해서 Object 의 prototype에 강제로 timeStamp 역활을 하는 메소드를 추가 합니다.

Object.prototype.timeStamp = function () {
 this.timestampObject = new Data();
}

이렇게 prototype에 추가한 모든 새로운 함수 또는 속성은 for in 반복연산이 수행될때 무조건 나타나게 되는데 이게 무슨 소리냐면 아래 그림과 코드를 보시지요.

// 아래 코드의 Insertion.Bottom 은 Prototype.js 의 클래스 메서드 이다.

Object.prototype.timeStamp = function() {
 this.timestamp = new Date();
}

function show(title, obj) {
 if(obj instanceof Array) {
 } else if(obj instanceof Object) {
  showObj(title, obj);
 } else {
  showVal(title,obj);
 }
}

function displayVal(item){
  return (item!=null) ? ((item.toString) ? item.toString() : item) : "null";
}

function showObj(title,obj){
  var html="<div class='list'>"
    +"<div class='listHdr'>"+title+"</div>";
  for (i in obj){
    var item=obj[i];
    var itemValue=displayVal(item);
    html+="<div class='listItem'>"+i+" : "+itemValue+"</div>";
  }
  html+="</div>";
  new Insertion.Bottom($('output'),html);
}

var details = {
 type : 'good',
 keyword : ["1","2","3","4","5","6"]
};

details.timeStamp();

show("details", details);

위 코드를 실행 하면 아래 그림과 같은 결과가 나타나네요.

for in 문으로 인해 함수정의도 반복연산을 수행할때 같이 출력됩니다.
(아아.. 물론

if( typeof obj[i] == "function" ) continue; 
이 코드를 넣으면 함수는 제외 시킬수 있다. 하지만 이건 분명히 자원낭비 임이 틀림없다
Object 를 prototype 함으로 인해 모든 객체가 속성과는 상관없는 함수정의를 들고 있기 때문이다 )

이러한 이유로 Object를 prototype 하는건 자제 하기 바랍니다. 그대신 바로 Object의 extend() 메소드를 이용하면 됩니다.

긴 길을 돌아 온거 같습니다. 이제 다시 본론으로 돌아가 extend() 메소드를 이용해서 객체의 계층구조를 만들어 보겠습니다(상속)

extend() 는 기본적으로 하나의 객체를 다른 객체의 기능을 추가하여 확장하는데에 이용되는데서 prototype과 비슷하게 보이지만
prototype은 prototype 프로퍼티를 이용하는 반면에 extend() 는 prototype 이 아니라 Object에 직접 추가가 됩니다.
코드를 통해서 살펴 보도록 하지요

 var picture = {
  id : 3
  title : "middle way",
  date : new Date("08/13/2008");
  details : {
    type : "photo",
    keywords : "green, red"
  },
  getAge : function() { return new Date() - this.date; }
 }

 Object.extend(
   picture.details,
   {
    camera : "nicon",
    location : "korea seoul",
    user : "DragonFly"
   }
 );
 
 show("extend details", picture.details);

실젝 출력결과는 올려진 코드를 통해 직접 확인 하세요 .
위 코드는 실제 오버라이딩 효과도 나타낼수 있습니다. 아래 처럼요

 var picture = {
  id : 3
  title : "middle way",
  date : new Date("08/13/2008");
  details : {
    type : "photo",
    keywords : "green, red"
  },
  getAge : function() { return new Date() - this.date; }
 }

 Object.extend(
   picture.details,
   {
    type : "picture",
    camera : "nicon",
    location : "korea seoul",
    user : "DragonFly",
    about: function() { return "picture.details type is : " + this.type }
   }
 );
 
 show("about", picture.details.about());

이제 언뜻 보기에 상속처럼 보이는 것도 되었고 생성자도 만들었네요. 이제 접근제어(private, public)만 하게 되면
거의 일반적인 OOP처리처럼 보일거 같습니다. 계속 작업을 진행해 보도록 하지요.

접근제어를 하기위해서는 간단한 클로저가 필요합니다. 이걸 처음 발견한 혹은 알린 더글러스 크록포드 씨는 영웅으로 취급 받고 있습니다.
설명 안하고 할려고 했는데.. 해야 할거 같아서 간단한 코드를 넣었습니다. 자바에는 없는 개념이라 이해 하기 어려울 수도 있지만 보면 이해 되니 걱정 마세요.

function foo() {
  var a = 10;
  function bar() {
    a *= 2;
  }
  bar();
  return a;
}

이 와같이 처리 하는 경우는 충분히 자바에서도 보셨을거 같습니다. 헌데... 다음과 같은 경우는 보셨나요?

function foo() {
  var a = 10;
  function bar() {
   a *= 2;
   return a;
  }
  return bar;
}

var baz = foo(); // baz 지금부터 function bar 를 참조합니다.
baz(); // returns 20.
baz(); // returns 40.
baz(); // returns 80.
var blat = foo(); // blat 은 bar의 또다른 참조를 만듭니다.
blat(); // returns 20

이와같은 형태가 클로저의 형태중 하나 입니다. 저럴 경우 위에서 보신바와 같이 a 라는 지역변수는 자신의 영역을 벗어나 전역변수 처럼 되지요.
더 파고 들면 순환 참조 부터 메모리릭 까지 이야기 해야 하니 이건 여기까지 설명 하고 바로 private를 어떻게 구현하는지 보여드리겠습니다.

function rectangle(w, h) {
 this.getWidth = w;
 this.getHeight= h;
}

rectangle.prototype.area = function() {
 return this.getWidth* this.getHeight;
}

위 코드와

function rectangle(w, h) {
 this.getWidth = function () {return w};
 this.getHeight= function () {return h};
}

rectangle.prototype.area = function() {
 return this.getWidth() * this.getHeight();
}

이 코드는 어떤 차이름 가지고 있을까요?
첫번째 경우에 다음과 같은 코드를 작성할수 있다

var oneRec = new rectangle(4, 8);
alert(oneRec.area());

//이때 불특정 사용자중 한명이 변칙적인 스크립트를 집어 넣었다
oneRec.getWidth  = 8;
oneRec.getHeight = 16;
alert(oneRec.area());

보시다시피 이미 만들어진 객체의 값을 변경할수 있다는걸 확인할수 있습니다. 이거 도형이 이니까 그러가 보다 하고 넘어 갈수 있지만 만약 돈일 경우에는 문제가 엄청나게 커질 수 도 있는 문제입니다.

두번째 경우를 살펴 보시면 음.. 어떻게 접근할 방법이보이지 않네요.. 즉 private 처리가 되었다는 이야기 입니다.
이제 이런 샘플 코드가 아니라 아까 작성한 Book 클래스를 이용해서 인터페이스(인터페이스 코드는 아래에 있습니다) 까지 적용해서 코드를 작성해 보겠습니다.

var BookInterface = new Interface('BookInterface', ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'setAuthor', 'display']);

var Book = function(newIsbn, newTitle, newAuthor) {
  var isbn, title, author; //private member
  
  this.getIsbn = function() {return isbn;};
  this.setIsbn = function(newIsbn) { isbn = newIsbn; };
  this.getTitle = function() {return title;};
  this.setTitle = function(newTitle) { title = newTitle || 'No title specified'; };
  this.getAuthor = function() { return author; };
  this.setAuthor = function(newAuthor) { author = newAuthor || 'No author specified'; };

  // Constructor code.
  this.setIsbn(newIsbn);
  this.setTitle(newTitle);
  this.setAuthor(newAuthor);
}

Book.prototype = {
  display: function() {
   alert('do anyting Whatever');
  }
};

var murder1 = new Book('123345', 'ramvo', 'killer');
Interface.ensureImplements(murder1 ,BookInterface ); // 인터페이스 구현여부 체크 

murder1.isbn = '123'     // 1
alert(murder1.isbn)      // 2
alert(murder1.getIsbn()) // 3

직접 실행해 보고 어떤차이가 있는지 생각해 보세요.

Object 의 extends() 키워드를 이용한 상속 부터 기타 여러가지 내용을 살펴 보았는데요.
상속에 대해 자바와 비교해보면서 좀더 살펴 보도록 하겠습니다

이 그림은 자바를 하면서 상당히 자주 보셨을 다이어 그램 입니다. 이를 좀더 상세화 시켜서 보면 다음과 같습니다.

이제 이 코드가 자바와 자바스크립트에서 어떻게 구현이 되는지 비교해 보겠습니다.

javascript java
 
function Employee () {
 this.name = "";
 this.dept = "general";
} 
public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this.name = "";
      this.dept = "general";
   }
} 

다음 매너저 클래스는 상속에 의해 구현이 되는 자바스크립트에서는 아래와 같은 방법을 체인 이라고 표현 합니다.
inheritance chain, Prototype Chain 이라고도 하지요.

javascript java
 
function Manager () {
  this.reports = [];
}
Manager.prototype = new Employee;

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee; 
public class Manager extends Employee {
   public Employee[] reports;
   public Manager () {
      this.reports = new Employee[0];
   }
}

public class WorkerBee extends Employee {
   public String[] projects;
   public WorkerBee () {
      this.projects = new String[0];
   }
}

계속해서 진행 하도록 하겠습니다.

javascript java
 
function SalesPerson () {
   this.dept = "sales";
   this.quota = 100;
}
SalesPerson.prototype = new WorkerBee;

function Engineer () {
   this.dept = "engineering";
   this.machine = "";
}
Engineer.prototype = new WorkerBee;
public class SalesPerson extends WorkerBee {
   public double quota;
   public SalesPerson () {
      this.dept = "sales";
      this.quota = 100.0;
   }
}

public class Engineer extends WorkerBee {
   public String machine;
   public Engineer () {
      this.dept = "engineering";
      this.machine = "";
   }
}

좀더 진행해 나가도록 하지요. 자바에서는 하나의 클래스가 여러개의 생성자를 가지는 경우가 있습니다 바로
아래 상황 처럼요. 그럴때 자바와 자바스크립트가 어떻게 구현되는지 한번 비교해 보도록 하지요.

javascript java
 
function Employee (name, dept) {
  this.name = name || "";
  this.dept = dept || "general";
}
public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this("", "general");
   }
   public Employee (String name) {
      this(name, "general");
   }
   public Employee (String name, String dept) {
      this.name = name;
      this.dept = dept;
   }
}
 
function WorkerBee (projs) {
this.projects = projs || [];
}
WorkerBee.prototype = new Employee;
 
public class WorkerBee extends Employee {
   public String[] projects;
   public WorkerBee () {
      this(new String[0]);
   }
   public WorkerBee (String[] projs) {
      this.projects = projs;
   }
}
 
function Engineer (mach) {
   this.dept = "engineering";
   this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
 
public class Engineer extends WorkerBee {
   public String machine;
   public WorkerBee () {
      this.dept = "engineering";
      this.machine = "";
   }
   public WorkerBee (String mach) {
      this.dept = "engineering";
      this.machine = mach;
   }
}

자바스크립트를 처음 사용하는 사람은 * this.name = name || ""; * 이 문법이 이상하게 느껴질수도 있지만 자바스크립트에서만 사용되는 OR의 변칙적인 사용법입니다.
직관적으로 이해 할수있는 부분이니 쓰면 쓸수록 사용능력이 증가 합니다.

WorkerBee.prototype = new Employee 위 예제에서 이와 같이 prototype 하는 부분에 부모 생성자를 대입 하는 방법이 Prototype Chain 이라고 하면서 상속이라는 이야기를 하며
이야기를 전개 하였습니다. 이에 대해 좀더 알아보도록 하지요.

기본학습 을 참고 하시면 충분히 그 원리가 이해 되겠지만 여기까지 와서 그런 이야기를 하게 되면
학습의 흐름이 끊어질 우려가 있기게 조금씩 설명을 하며 진행을 하겠습니다. (자세한 내용을 잘 읽어 보시면 나옵니다.)

/* Class Person. */
function Person(name) {
 this.name = name;
}
Person.prototype.getName = function() {
 return this.name;
}

var reader = new Person('dragonfly');
alert(reader.getName());

/* Class Author. */
function Author(name, books) {
 Person.call(this, name); // 부모클래스의 생성자 호출
 this.books = books;
}

Author.prototype = new Person(); 
Author.prototype.constructor = Author; // 이렇게 하는 이유는 멀까요? 주석을 한경우와 안한경우의 차이를 확인해보자 실제 실무에서는 이걸로 인한 문제는 발생되지 않지만 그래도 예외는 있다고 책에 써있더라.
Author.prototype.getBooks = function() { 
 return this.books;
};

var author = [];
author[0] = new Author('killer1', ['John Rambo']);
author[1] = new Author('killer2', ['Comando']);
author[1].getName();
author[1].getBooks();

alert(author[0].getName() + "@@" + author[0].getBooks());
alert(author[1].getName() + "@@" + author[1].getBooks());
alert(Author.prototype.constructor);

이번에는 extends 라는 함수를 하나 생성해서 상속을 구현해 보지요~

/* Extend function. */
function extend(subClass, superClass) {
 var F = function() {};
 F.prototype = superClass.prototype;
 subClass.prototype = new F();
 subClass.prototype.constructor = subClass;
}

/* Class Person. */
function Person(name) {
 this.name = name;
}
Person.prototype.getName = function() {
 return this.name;
}

/* Class Author. */
function Author(name, books) {
 Person.call(this, name);
 this.books = books;
}

extend(Author, Person);

Author.prototype.getBooks = function() {
 return this.books;
};

var author = [];
author[0] = new Author('killer1', ['John Rambo']);
author[1] = new Author('killer2', ['Comando']);
author[1].getName();
author[1].getBooks();

alert(author[0].getName() + "@@" + author[0].getBooks());
alert(author[1].getName() + "@@" + author[1].getBooks());

/* Extend function, improved. */
function extend(subClass, superClass) {
 var F = function() {};
 F.prototype = superClass.prototype;
 subClass.prototype = new F();
 subClass.prototype.constructor = subClass;
 subClass.superclass = superClass.prototype; //이렇게 하면 어떤장점이 있을까요?
 if(superClass.prototype.constructor == Object.prototype.constructor) {
  superClass.prototype.constructor = superClass;
 }
}

/* Class Author. */
function Author(name, books) {
 Author.superclass.constructor.call(this, name);
 this.books = books;
}
extend(Author, Person);

Author.prototype.getBooks = function() {
 return this.books;
};

Author.prototype.getName = function() {
  var name = Author.superclass.getName.call(this);
  return name + ', Author of ' + this.getBooks().join(', ');
};

var author = [];
author[0] = new Author('killer1', ['John Rambo']);
author[1] = new Author('killer2', ['Comando']);
author[1].getName();
author[1].getBooks();

alert(author[0].getName() + "@@" + author[0].getBooks());
alert(author[1].getName() + "@@" + author[1].getBooks());

다음 clone 메소드를 마지막 예제로 상속에 다한 샘플을 마무리 진다.

function clone(object) {
  function F() {}
  F.prototype = object;
  return new F;
}

var Person = {
  name: 'default name',
  getName: function() {
   return this.name;
  }
};

var reader = clone(Person);
alert(reader.getName()); // This will output 'default name'.
reader.name = 'John Smith';
alert(reader.getName()); // This will now output 'John Smith'.


var Author = clone(Person);
  Author.books = []; // Default value.
  Author.getBooks = function() {
    return this.books;
  }
  
var author = [];
author[0] = clone(Author);
author[0].name = 'Dustin Diaz';
author[0].books = ['JavaScript Design Patterns'];
author[1] = clone(Author);
author[1].name = 'Ross Harmes';
author[1].books = ['JavaScript Design Patterns'];

alert(author[0].getName());
alert(author[0].getBooks());  
alert(author[1].getName());
alert(author[1].getBooks());  

2. OOP특성인 클래스를 구현케하는 샘플 클래스 (코어자바스크립트)


3. OOP특성인 인터페이스를 강제한 샘플 클래스 (코어자바스크립트)

//  +-------------------------------------------------------------------------------------------------+
//  |	인터페이스 
//  +-------------------------------------------------------------------------------------------------+
//  |	작성자 : RossHarmes
//  |   수정자 : 허용운
//  |	작성일 : 2008-08-12
//  |	내  용 : 
//  |              1.인터페이스란 무엇인가?
//  |                  자바스크립트에는 인터페이스가 없습니다. 다른 OOP언어에서 인터페이스란 구현은
//  |                  없고 단순한 메소드의 선언만 존재하는 메소드 껍데기의 집합체 입니다.
//  |	           2.인터페이스의 사용이유
//  |                  oop언어에서 인터페이스의 사용이유는 인터페이스를 Implements(구현) 하는 클래스
//  |                  들은 인터페이스의 메소드들을 무조건적으로 구현 해야 ?니다.
//  |                  즉 인터페이스를 구현한 클래스의 타입을 강제(제약) 할수 있는 이유로 인터페이스
//  |                  를 사용하게 됩니다.
//  |              3.샘플예제
//  |                  아래 샘플 예제는 다음과 같이 인터페이스의 목적을 보여주기 위해 작성
//  |                  되었습니다.
//  |                  new Interface 를 통하여 SampleInterface 를 만들고  SampleInterface를 
//  |                  구현한 sampleObject를 만든후에 sampleObject가 과연 SampleInterface에 의해 
//  |                  강제 되는 지를 확인 하게 됩니다.
//  +-------------------------------------------------------------------------------------------------+

var Interface = function(name, methods) {
  if(arguments.length != 2) {
      throw new Error("인터페이스생성자의 아규먼트 길이가 다음과 같습니다. : " + arguments.length + " 하지만 인터페이스를 생성하기 위해 필요한 인자의 수는 2 입니다.");
  }
  this.name = name;
  this.methods = [];
  for(var i = 0, len = methods.length; i < len; i++) {
    if(typeof methods[i] !== 'string') {
      throw new Error("인터페이스 생성자의 메소드명은 string 타입이어야 합니다.");
    }
    this.methods.push(methods[i]);
  }
};

Interface.ensureImplements = function(object) {
    if(arguments.length < 2) {
      throw new Error("Interface.ensureImplements 메서드의 아규먼트 길이가 다음과 같습니다." + arguments.length + "아규먼트의 길이는 2 보다 커야 합니다..");
    }
    for(var i = 1, len = arguments.length; i < len; i++) {
      var interface = arguments[i];
      if(interface.constructor !== Interface) {
        throw new Error("Interface.ensureImplements 메서드는 아규먼트로 두개 이상의 인테페이스 객체를 요구 합니다.");
      }
      for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
        var method = interface.methods[j];
        if(!object[method] || typeof object[method] !== 'function') {
          throw new Error("Interface.ensureImplements: " + interface.name  + " 이름으로 구현된 객체 에서 interface. Method 인 " + method + " 가 구현되어 있지 않습니다.");
        }
      }
    }
};

var SampleInterface = new Interface('sampleInterfaceMethodMap', ['sampleMethod1','sampleMethod2','sampleMethod3'])
var sampleObject = new Object();

sampleObject.sampleMethod1 = function() {};
sampleObject.sampleMethod2 = function() {};
sampleObject.sampleMethod3 = function() {};


Interface.ensureImplements(sampleObject,SampleInterface)
sampleObject.sampleMethod1();
sampleObject.sampleMethod2();
sampleObject.sampleMethod3();

4. 이벤트의 이해 및 이벤트 중심의 스크립트의 활용 (클라이언트측 자바스크립트)

이건 나중에....

5. 자바스크립트 프레임워크를 사용한 이펙트 기능 구현 ( 클라이언트측 자바스크립트)

이건 나중에....

문서에 대하여

문서정보

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.