login register Sysop! about ME  
qrcode
    최초 작성일 :    2007년 11월 13일
  최종 수정일 :    2007년 11월 24일
  작성자 :    히딩크
  편집자 :    히딩크(정 석모)
  읽음수 :    25,547

강좌 목록으로 돌아가기

필자의 잡담~

공부하기 좋은 겨울이 오고 있습니다~
이 강좌의 순서

객체지향 자바스크립트 프로그래밍
강좌 시작 전 1분 스피치

매우 안녕하십니다! 히딩크 입니다!
JS OOP 강좌 3편을 기다려주신 5천800여명(자체추정)의 개발자 분들께 심심한 캄사의 말씀을 드립니다.

누군가가 여러분께 "객체(Object)란 무엇입니까?"라고 질문을 했습니다.
매우 기초적인 질문이며 간 보는 질문이지만 여러분은 확실하게 대답을 해야 합니다. 저의 강좌를 읽으신 여러분은 이미 객체와 친구니까요!

이렇게 대답 해 주세요. "객체는 Unique Identyty(이름), State(어트리뷰트), Behavior(메서드)를 가진 사용자 정의 데이터 타입입니다." 발음도 적절하게 굴려 주세요.

질문을 던진 상대방은 이렇게 말해줄 겁니다. "우왕ㅋ굳ㅋ"

또 한가지! "Object"의 발음은 "아압직트"입니다. 혹시 외국인과 대화 할 일 있으면 "오브젝트"라고 하지 마세요. 못 알아 들을 가능성이 있습니다. ^^

자 그럼 강좌 시작 하겠습니다!


사용자 정의 객체를 만들어 보자구요!

자 이제 나만의 사용자 정의 객체를 만들어 보겠습니다! 준비물은 텍스트 편집기와 거들 수 있는 왼손입니다. 일단 소스부터 보시겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    function myClass( p )
    {
        this.Comment    = "나는 사용자 정의 객체입니다.";
        this.v1 = p;
        v2 = "텔미텔미";
    }

    function myClass_toString()
    {
        return "v1=" + this.v1;
    }

    function myClass_setValue( p )
    {
        this.v2 = p;
    }

    function myClass_getValue()
    {
        return this.v2;
    }

    myClass.prototype.toString         = myClass_toString;
    myClass.prototype.setValue        = myClass_setValue;
    myClass.prototype.getValue        = myClass_getValue;
    myClass.prototype.resetValue     = function(){ this.v1 = null; this.v2 = null; }

    var o = new myClass( "1" ); //인스턴스야 생겨라! 뿅!

    alert(o.Comment);//나는 사용자 정의 객체입니다.
    alert(o.v1); //1
    alert(o.v2); //undefined
    alert(o); //v1=1
    o.resetValue();
    alert(o); //v1=null
    o.setValue("I am Private");
    alert(o.getValue()); //I am Private

EBS에서 그림을 그리시던 밥 아저씨가 말씀 하십니다. "참 쉽죠?"

우리는 여기서 한 가지 중요한 사실을 눈치 챌 수 있습니다. "오잉? function은 함수일 뿐인데 객체를 선언할 때 쓰일 수 있어??"

그렇습니다! 자바스크립트에서는 함수도 객체 입니다!

자바스크립트에서는 함수(function)도 객체로 취급되며, 어트리뷰트와 메서드 또한 존재 합니다. 함수가 객체로 취급되니 당연히 함수의 인스턴스도 뿅 하고 생기겠죠. 이는 자바스크립트가 다른 언어와 다른 점 중 한가지 입니다(다른 언어에서 함수는 이름 그대로 함수일 뿐입니다). 덕분에 class 키워드 없이도 객체를 쉽게 정의 할 수 있습니다. (다음에 기회가 되면 함수와 그에 따르는 이슈들에 대해서 정리를 해 드리겠습니다.)

자 그럼 제가 또 무슨 짓을 벌였는지를 차근차근 알아 봅시다!

 1
 2
 3
 4
 5
 6
    function myClass( p )
    {
        this.Comment    = "나는 사용자 정의 객체입니다.";
        this.v1 = p;
        v2 = "텔미텔미";
    }

드디어 우리의 첫 사용자 정의 객체를 선언하는 순간입니다. 우리가 만든 객체의 이름은 "myClass" 입니다. 어트리뷰트는 세 개(Comment, v1, v2) 입니다. 메서드는 아직 없습니다만 우리의 친구인 prototype이 확장을 도와 줄 겁니다.

그런데 무언가 눈에 띄는 것이 세 가지 있죠?

첫 번째, 함수 선언에 인자가 있다! 함수이자 객체인 myClass의 선언은 Constructor(생성자)의 역할을 담당하고 있습니다. 우리가 알고 있는 생성자의 역할은 "어트리뷰트 초기화 하기"와 "메모리에 내 인스턴스 적재하기" 입니다. 따라서, myClass 생성자는 우리가 기대했던 역할들을 성실하게 수행합니다.

두 번째, this 키워드! this는 자기 자신을 참조하는 키워드 입니다.

세 번째, 왜 v2에는 this 키워드를 쓰지 않았습니까? OO(Object Oriented)의 특징 중 한가지가 은폐(Encapsulation)입니다. 눈치 채셨나요? this가 있고 없고의 차이는 PublicPrivate의 구분입니다(요거 아주 중요 합니다). this 키워드를 사용하여 어트리뷰트나 메서드를 선언하면 Public 선언이며, this 키워드를 사용하지 않고 어트리뷰트나 메서드를 선언하면 Private 선언입니다. 이 것을 실제로 사용할 때에는 신중하게 사용해야 합니다. 만약 Private로 선언한 어트리뷰트를 외부에서 직접 참조하고자 한다면 "undefined"가 반환되기 때문에 기대하지 못 했던 오류가 발생할 수 있으며, 김부장님에게 혼날 수 있습니다. 우리는 적절한 setter와 getter를 만들어 서 기대하지 못했던 오류를 줄일 수 있습니다.


자 이제 객체에 살을 붙여 볼 차례죠? 다 같이 외쳐 봅시다. Help me~ My friend, prototype!

 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    function myClass_toString()
    {
        return "v1=" + this.v1;
    }

    function myClass_setValue( p )
    {
        this.v2 = p;
    }

    function myClass_getValue()
    {
        return this.v2;
    }

    myClass.prototype.toString         = myClass_toString;
    myClass.prototype.setValue        = myClass_setValue;
    myClass.prototype.getValue        = myClass_getValue;
    myClass.prototype.resetValue     = function(){ this.v1 = null; this.v2 = null; }



센스가 충만하신 여러분들은 모두 눈치 채셨죠? 우리의 소중한 친구인 Prototype의 도움으로 세 개의 새로운 메서드( setValue, getValue, resetValue )를 확장 했으며 한 개의 메서드( toString )을 오버라이딩(Overriding) 했습니다. 눈썰미가 있으신 분들은 이렇게 질문을 던지실 겁니다.

"왜 'resetValue' 메서드의 확장은 다른 세 개의 선언과 틀리죠?"

결론부터 말하자면, myClass_toString, myClass_setValue, myClass_getValue처럼 함수의 식별자(이름)으로 할당을 하는 경우는 "함수 포인터(Pointer)"를 할당하는 경우이며, resetValue는 함수 리터럴을 사용하여 함수를 할당한것입니다. 이 때 원더걸스의 안소희 씨가 매우 궁금하다는 표정으로 질문을 던집니다. 아흥~

"옵빠! 함수 포인터가 뭐예용? 텔미 텔미 테테테테텔미"

우리의 또 다른 친구인 자바스크립트 엔진은 자바스크립트 코드를 실행하기 전에 코드를 미리 파싱해서 객체의 인스턴스를 메모리제 적재 시켜 놓습니다. 이 때, 함수의 인스턴스도 메모리에 적재 시킵니다(함수도 객체로 취급되기 때문이죠). 결국, 함수의 식별자를 이용한 할당은 실행 시점에 "함수 포인터(Pointer)"가 가리키고 있는 함수의 내용을 복사해서 객체에 끼워 넣는 것입니다.

함수 포인터를 사용하지 않은 resetValue 메서드의 확장은 함수 리터럴(literal)을 사용한 확장입니다. 즉, 함수의 내용을 직접 할당하는 것입니다.

자~ 지금까지 myClass라는 이름을 가진 객체를 설계 했으니 인스턴스를 만들어서 사용 해 봅시다.

28    var o = new myClass( "1" ); //인스턴스야 생겨라! 뿅!

new 키워드를 사용해서 객체의 인스턴스를 생성한 다음 "o"라는 변수(객체변수)에 할당 했습니다. 이런 코드는 다른 언어에서는 상상조차 할 수 없는 코드 이며 가차없이 에러를 뿅 하고 띄웁니다. 원래는 다음과 같이 선언을 해야 하지만,

myClass o = new myClass( "1" );

자바스크립트의 관대함 덕분에 데이터 타입을 명시 하지 않아도 됩니다(아니, 하고싶어도 할 수가 없습니다. ㅜㅜ)

30
31
32
33
    alert(o.Comment);//나는 사용자 정의 객체입니다.
    alert(o.v1); //1
    alert(o.v2); //undefined
    alert(o); //v1=1


위의 코드는 접근자 쩜(.)을 사용해서 메서드의 어트리뷰트를 경고 창에 뿅~ 하고 출력 하는 코드 입니다. 위의 코드에서 눈 여겨 보아야 할 라인은 32 라인과 33 라인입니다.

그 전에! alert()에 대해 약간의 탐구를 해 보겠습니다. 우리가 매우매우 자주자주 사용했었던 전역 함수 alert()는 인자로 제공되는 객체의 문자열 표현인 toString()을 호출 합니다. 인자로 객체가 제공 된다면 해당 객체의 toString() 오버라이딩(Overriding) 구현을 찾아봅니다(lookup). 만약 toString()이 구현되지 않았다면 super 객체인 Object 객체의 toString() 반환 값인 "[object Object]"를 출력합니다. 모든 객체는 Object를 상속받은 자식들이기 때문이죠. 이 것이 상속 메커니즘이며 OO(Object Oriented)의 매력 포인트 중 하나입니다.

하지만 alert()의 인자로 객체가 제공되지 않고 기본 데이터(Primitive Data)가 제공 된다면 어떤 메커니즘을 통해 문자열이 출력 될까요?

우리의 자바스크립트 엔진은 기본 데이터를 다룰 때 적절한 객체( "제임스 String", "마가렛 Number", "샬럿 Date", "마이클 빅 Math", "윌리엄 Array" )로 래핑(wrapping) 해서 다룹니다. 이는 형 안전성(Type Safety)를 유지하기 위함입니다. 결국 alert()는 제공된 기본 데이터 타입에 래핑된 객체의 toString() 구현을 찾아보게 됩니다(물론 코어에 미리 정의되어 있습니다 ^^).

오옷! 자바스크립트의 관대함 뒤에는 안전성을 유지하려는 노력이 존재하는군요. 눈물 나게 감사합니다. 뭐 합니까! 박수 한번 쳐 주세요. 짝짝짝~~

이제 32 라인과 33 라인을 살펴 보도록 하죠!

- 32라인 : alert(o.v2);
위에서 언급했듯이 v2는 this 키워드를 사용하지 않고 선언되었기 때문에 Private 어트리뷰트 입니다. 따라서 자바스크립트 엔진은 직접적인 접근에 대해 과감히 "undefined"를 반환합니다. 이럴 때 필요한 것은? 그렇습니다! 적절한 setter와 getter이며 우리는 이미 setValue와 getValue로 구현을 해 놓았습니다. ^^v

- 33라인 : alert(o);
우리의 myClass 객체는 toString() 메서드를 오버라이딩 했기 때문에 미리 약속한대로 "v1=[v1의 값]"이 출력됩니다.

계속 해서 다음 코드 나갑니다!

34
35
36
37
    o.resetValue();
    alert(o); //v1=null
    o.setValue("I am Private");
    alert(o.getValue()); //I am Private


바로 위에서 설명했던 코드와 별반 다를 것이 없습니다. 하지만 Private 어트리뷰트에 접근하는 setter와 getter를 사용 했습니다. 이와 같이 변경에 민감한 데이터들에 대해서는 Private로 선언해서 접근을 제한적으로 허용하는 것이 좋습니다.

자! 우리는 이제까지 사용자 정의 객체를 만들고 직접 사용 해 보았습니다. 이제 여러분은 객체를 만들어서 적용할 준비가 되어 있습니다.



아부지! 재산을 저에게 상속(Inheritance)해 주세요!

자 이제부터 본격적으로 OO(Object Oriented)의 대표적인 특징인 상속을 다루어보도록 하겠습니다.

도대체 상속(Inheritance)은 뭔데?
  • 쉬운 비유 #1 : 아버지의 재산을 아들이 상속받는다.
  • 쉬운 비유 #2 : 아들은 아버지의 유전적 특성(어트리뷰트)과 습관(메서드)를 닮아간다(상속).
  • 부모 객체(super object)의 모든 것(어트리뷰트, 메서드, 생성자)를 자식 객체(sub object)가 상속받는다.
  • 자식 객체는 부모 객체가 상속해준 것을 재정의 할 수 있다(부모 객체의 일반화).
  • Module의 공통적인 부분을 묶을 수 있어서 동일한 의미를 가지는 코드를 한 번 이상 작성할 필요가 없다.
  • 재사용성, 유지보수성, 확장성의 이점을 얻을 수 있다.

자바스크립트에서는 객체의 상속 관계를 어떻게 구현 할까요? 사실 자바스크립트 1.3 버전 이하에서는 상속을 구현할 방법이 없었습니다. 1.3 버전부터 상속을 구현하기 위한 apply와 call 함수를 제공하기 시작했습니다. 오늘부터 apply와 call 함수를 친구추가 해 주세요.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
    function Father( p1, p2 )
    {
        this.FatherMoney = p1 * p2;
    }

    Father.prototype.Say = function(){ alert("아부지의 용돈은 " + this.FatherMoney + "원"); }

    function Son( p )
    {
        Father.call( this, p, 2 );
        this.SonMoney = p;
    }

    Son.prototype                 = new Father();
    Son.prototype.Say          = function(){ alert("아들의 용돈은 " + this.SonMoney + "원"); }
    Son.prototype.FatherSay = function(){ Father.prototype.Say.call( this ); }

    var o = new Son(100);

    o.Say();
    o.FatherSay();

조금 이해하기 힘드신 부분이 있으실 겁니다. 차근차근 따라와 보세요. ^^

 1
 2
 3
 4
 5
 6
    function Father( p1, p2 )
    {
        this.FatherMoney = p1 * p2;
    }

    Father.prototype.Say = function(){ alert("아부지의 용돈은 " + this.FatherMoney + "원"); }

부모 객체인 Father를 선언 했습니다. FatherMoney라는 어트리뷰트와 Say라는 메서드를 가지고 있습니다.

 8
 9
10
11
12
13
14
15
16
    function Son( p )
    {
        Father.call( this, p, 2 );
        this.SonMoney = p;
    }

    Son.prototype                 = new Father();
    Son.prototype.Say          = function(){ alert("아들의 용돈은 " + this.SonMoney + "원"); }
    Son.prototype.FatherSay = function(){ Father.prototype.Say.call( this ); }

Father 객체를 상속 받을 Son 객체 입니다. Father 객체의 FatherMoney 어트리뷰트와 Say 메서드를 상속 받으며, Say 메서드를 재 정의 해서 보다 일반화 하였습니다(엄밀히 말해 이 예제는 일반화 한 것은 아니지만 대부분 일반화 합니다).

- 10 라인 : Father.call( this, p, 2 );
call과 apply는 다른 컨텍스트의 객체를 사용할 수 있게 해 주는 함수 입니다. call 함수의 원형은 Function.call( this, arg1, arg2, ..., argN ) 이며, apply 함수의 원형은 Function.apply( this, arguments ) 입니다(arguments는 함수에 전달 된 인자 값들을 배열로 가지고 있는 Function 객체의 고유 프로퍼티 입니다). 상위 객체 생성자의 인자 집합과 하위 객체 생성자의 인자 집합이 동일하다면 apply를 사용하며, 서로 다르다면 call을 사용합니다. 우리가 다루는 예제에서는 생성자의 인자 집합이 서로 다르기 때문에 call을 사용하였습니다. 그리고, 해당 객체를 사용하는 컨텍스트가 내 자신이기 때문에 첫 번째 인자는 무조건 this 입니다. 이 선언으로 Father 객체의 모든 것을 사용하겠다는 사실을 자바스크립트 엔진에 알려주게 되는 것입니다.

- 14 라인 : Son.prototype = new Father();
아버지가 먼저 태어났을까요? 아들이 먼저 태어났을까요? 스스로 그러한 자연의 법칙에 따르면 아버지가 먼저 태어난 후에 아들이 태어나는 것이 당연합니다(난 그러지 않았다고 대답 하시면 매우 난감 합니다 ㅋㅋ). 이 법칙은 객체에 그대로 적용 되며, 부모 객체의 인스턴스가 생성된 후에 자식 객체가 생성되는 것이 순서입니다. 이 것이 생성자 체이닝(Chaining) 입니다. 이 코드가 수행되면 Father의 인스턴스가 생성 된 후(생성자 호출)에 Son에 할당됩니다.

- 15 라인 : Son.prototype.Say = function(){ alert("아들의 용돈은 " + this.SonMoney + "원"); }
Son 객체는 Father 객체의 Say() 메서드를 상속받았지만 마음에 들지 않았던지 자신의 것으로 재 정의 했습니다. 요것이 메서드 오버라이딩(Overriding) 입니다.

- 16 라인 : Son.prototype.FatherSay = function(){ Father.prototype.Say.call( this ); }
위에서 말씀드린 call함수를 사용하여 현재 컨텍스트 안에서 부모 객체의 메서드를 호출 하는 코드 입니다. 왜 이렇게 어렵게 하냐구요? 애석하게도 자바스크립트에서는 super 키워드가 없습니다. 그렇기 때문에 call을 사용하여 부모 객체의 메서드를 호출 하였습니다. super.Say()처럼 부모 객체의 메서드를 직접 호출하는 방법은 없습니다.

"자바스크립트가 OO를 지원 한다면서 왜 super 키워드를 지원하지 않습니까?"

때가 왔습니다. 급 깜짝, 급 실망 발표를 하겠습니다. 자바스크립트의 OOP는 진정한 의미의 OOP가 아닙니다. OOP의 기본인 다형성도 지원하지 않으며 추상화도 지원하지 않습니다. 그저 약간의 흉내만 낼 뿐입니다. 우리는 자바스크립트 OOP 나름대로의 장점을 잘 활용해서 효율을 극대화 시켜 사용하기만 하면 그만입니다. 여러분 사랑합니다.

여기까지가 상속의 선언 단계에 속합니다. 이제 상속받은 Son 객체를 사용 해 봅시다.

18
19
20
21
    var o = new Son(100);

    o.Say();
    o.FatherSay();

이제 new 키워드가 매우 친숙해졌죠?

Son 객체의 인스턴스를 생성한 후 Say() 메서드를 호출 했습니다. Father.Say()를 호출하고 싶지만 super 키워드를 지원하지 않기 때문에 우회적인 방법으로 FatherSay() 메서드를 호출 했습니다. "아부지의 용돈은 200원", "아들의 용돈은 100원"이란 경고 창이 사랑스럽게 출력 될 겁니다. 뿅~


요약

이제 여러분은 나만의 객체를 생성하고 여러 곳에 상속을 해 줄 준비가 되어 있습니다. 지금까지 살펴보았던 내용을 가지고 나만의 프레임워크를 만들 수 있습니다. 여러분 신나죠?? 꺄악~!@#$%

만약 객체지향 개념이 약하시다면 철저하게 습득하시길 권해드립니다. 웹 개발로 시작한 개발자의 시각이 모든 것들을 객체로 보이는 시각으로 진화할 때 반가운 손님이 찾아올 것입니다. 바로 해탈의 시기가 찾아오게 되며 사춘기를 지나 진정한 어른이 되는 순간입니다.

다음 시간에는 나만의 프레임워크를 만들어 보도록 하겠습니다.

그럼! 채널 고정!

authored by

  bandcy
  2008-12-09(11:35)
캐릭 이미지
높은 내공과 필력으로 재미있는 강좌를 써 주셔서,
재미있게 잘 봤습니다.
자바스크립트에 대한 호기심을 매우 자극하는 강좌였습니다.


 
 
.NET과 Java 동영상 기반의 교육사이트

로딩 중입니다...

서버 프레임워크 지원 : NeoDEEX
based on ASP.NET 3.5
Creative Commons License
{5}
{2} 읽음   :{3} ({4})