[자바스크립트] 객체지향적으로 자바스크립트 사용하기(미작성)

류명운

·

2016. 8. 21. 23:17

반응형

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


참고 : http://wiki.gurubee.net/pages/viewpage.action?pageId=1507885



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

  • 1. 자바스크립트에서 OOP를 가능하게 하는 요소 (코어자바스크립트)
  • 2. OOP특성인 클래스를 구현케하는 샘플 클래스 (코어자바스크립트)
  • 3. OOP특성인 인터페이스를 강제한 샘플 클래스 (코어자바스크립트)
  • 4. 이벤트의 이해 및 이벤트 중심의 스크립트의 활용 (클라이언트측 자바스크립트)
  • 5. 자바스크립트 프레임워크를 사용한 이펙트 기능 구현 (클라이언트측 자바스크립트)


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

이전 포스팅 'prototype.js와 객체지향'에 대한 선행학습을 진행했다는 가정하게 본 포스팅 작성을 이어진행하도록 하겠습니다.


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

OOP(object-oriented programming)란 다들 알다 싶이 코드의 극단적 재활용을 추구하면서 나타난 개념이다.

인간은 기본적인 사고 흐름(절차적)과 다른 흐름(이벤트 중심적?)을 가지고 있어서 초보개발자는 OOP로 개발하기가 쉽지 않다. 대표적인 경우가 OOP언어인 자바와 C#을 가지고 C처럼 프로그래밍 하는 경우이다(main에 프로그램을 다 기술한다던데.. 하는 경우)

자바스크립트는 OOP언어 임에도 불구하고 그 태생과는 별계로 그동안 이벤트 중심으로 처리될 HTML화면에서 단순히 에러체크 로직 정도의 역할만 하는 언어로 자리잡고 있었다.

하지만 점차 컴퓨팅 환경의 발전과 웹의 발전에 따라 자바스크립트 또한 점점 복잡해졌고 이에 따라 재사용성을 고민하던 여러 선배개발자의 고민 덕분에 자바스크립트를 이렇게 써야 한다고 가이드라인까지 나오게 되었다. 그 가이드라인의 핵심이 되는게 바로 이전 포스팅에서 살펴본 prototype 프로퍼티 이다.

prototype는 OOP언어의 핵심인 상속. 즉 재사용성을 멋들어지게 구현할 수 있게 도와주는 개발자의 소중한 친구가 될것이다.


다른 언어에서 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'));
					}
				}	    
		    }	    
		}
	}();

객체 리터럴이라는 문법 체계를 사용하는 이유는 다음과 같다.

1) 어설프지만 그동안 봐온 OOP언어의 클래스 형태처럼 꾸밀 수 있어서

2) 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가지 클래스를 자바스크립트로 어떻게 개발해 나가는지 간단히 살펴보도록 하겠다.


다음 예제코드를 확인해보자.

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개의 차이점이 무엇인지 살펴보자.

 - Something(1, 2, 3) : 함수가 동작되었을 때 일반적으로 웹브라우저에서는 전달받은 매개변수들을 현재 컨텍스트 객체(아무것도 지정이 되어있지 않기 때문에 window 객체가 적용)의 프로퍼티로 저장한다.

 - var valueObj = new Something(1, 2, 3) : 지정된 함수를 OOP언어에서의 생성자로 바라보고 있다. (new 연산자는 새롭고 빈 Object 인스턴스를 생성한 다음에 함수를 호출하고 이 함수에 피연산자는 함수 호출 컨텍스트로서 새로 생성된 객체가 된다. 이로인해 함수가 실행될 때 this 포인터가 새로운 객체를 참조하고, 함수는 객체에 대한 생성자가 된다) 그러므로 valueObj 객체는 p1, p2, p3, 값을 가지고 있는 Param1, Param2, Param3 3개의 프로퍼티를 가지게 된다. 즉 캡슐화를 손에 넣었다.

이렇듯 2번째 방법으로 접근하면 상황이 달라진다. 이제 이 2번째 메커니즘을 이용해 좀 더 확장해 나가보자.

//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 );

.
.
.

위 예제코드에서 확인할 수 있듯이 여러 시리즈를 작성할 수 있게 되었다. 하지만 시리즈가 늘어나면 늘어날수록 위 예제코드는 info 메소드를 추가해야 한다. 이를 개선하기 위해 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 함수의 복사본을 소유하므로 메모리의 낭비가 발생하는 단점이 생긴다. (정말 그런지 확인하고 싶다면 웹브라우저의 디버그 툴을 사용해서 메모리 스택에 어떤구조로 값이 들어가는지 확인하길 바란다)

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="
" +"
"+title+"
"; for (i in obj){ var item=obj[i]; var itemValue=displayVal(item); html+="
"+i+" : "+itemValue+"
"; } html+="
"; new Insertion.Bottom($('output'),html); } var details = { type : 'good', keyword : ["1","2","3","4","5","6"] }; details.timeStamp(); show("details", details);

위 예제코드를 실행하면 다음과 같은 결과값이 나타난다.

  • details
  • type : good
  • keyword : 1,2,3,4,5,6
  • timestamp : Wed Aug 20 2016 13:15:35 GMT+0900
  • timeStamp : function () {this.timestamp = new Date; }

for in 문으로 인해 함수정의도 반복연산을 수행할 때 같이 출력된다.

물론 다음 코드를 삽입하면 함수는 제외 시킬 수 있다. 하지만 이건 분명히 자원낭비 임이 틀림없다.

if( typeof obj[i] == "function" ) continue;

(Object를 prototype 함으로 인해 모든 객체가 속성과는 상관없는 함수정의를 들고 있기 때문에 자원낭비)

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


extend() 메소드를 이용해서 객체의 계층구조를 만들어 보자(상속)

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


다음 예제코드를 확인해보자.


...(미작성)



반응형