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

5. prototype js와 객체지향




prototype js와 객체지향

prototype js 프레임 워크를 살펴 보기에 앞서서 먼저 자바스크립트의 사상대해 살펴 보고 넘어가야 합니다.
가장 기본적인 프로퍼티 prototype, _proto_, constructor.prototype ,constructor 이 4가지에 대해 살펴 봅시다.

객체지향의 기본개념인 상속(inheritance)을 이용해 어떻게 서브 클래스와 부모클래스의 관계를 맺어주는지 살펴봅시다.

prototype , constructor , constructor.prototype , _proto_

모든 함수에는 미리 정의된 prototype 객체를 가르키는 prototype 프로퍼티가 있다.
이 prototype 객체는 함수가 new 연산자를 통해 생성자 함수로 사용될 때, 새객체를 정의 하는 과정에서 중요한 역활을 하게 된다.

코드A 코드B
var Parent1 = function(name, age) {
 this.name = name;
 this.age  = age; 
 this.getInfo = function() { return "이름 : " + this.name + "   나이 : " + this.age ; }
}
var test1 = new Parent1('하세가와','20');
var test2 = new Parent1('하세가와','20');
var test3 = new Parent1('하세가와','20');

alert(test1.getInfo());
alert(test2.getInfo());
alert(test3.getInfo());

var Parent2 = function(name, age) {
 this.name = name;
 this.age  = age; 
}
Parent2.prototype.getInfo = function() {
 return "이름 : " + this.name + "   나이 : " + this.age
}

var test4 = new Parent2('하세가와','20');
var test5 = new Parent2('하세가와','20');
var test6 = new Parent2('하세가와','20');

alert(test4.getInfo());
alert(test5.getInfo());
alert(test6.getInfo());

코드 A와 코드 B는 같은 결과적으로 같은 정보를 리턴 한다. 하지만 메모리 관리 상에서 prototype를 이용한 방법은 좀더 영리하게 동작한다.
생성자 함수 Paren1 과 Parent2의 인스턴스를 만들어 낼 경우에 getInfo 라는 프로퍼티는 Parent1 쪽에선 객체마다 생성이 되어진다.
(파이어폭스의 파이어버그를 이용해서 확인해 보자) 하지만 Parent2의 경우에는 각각 생성자 함수를 통해 만들어진 인스턴스들이
prototype된 getInfo 프로퍼티의 레퍼런스를 공유 하고 있다. 그래서 메모리를 좀더 아껴서 사용 할수 있따.
물론 단순히 메모리 처리많을 위해 prototype이 사용되지는 않는다.
위 예제에서 좀더 유추해 낼수 있는 정보는 다른 OOP 언어에서 처럼 모든 객체가 공유 하는 정보 즉 static 처리가 가능 하다는걸 알아 낼수 있다.

prototype

prototype 객체는 OOP 의 상속(inheritance)과 관계된 중요한 객체이다.
prototype는 다른 OOP언어에서는 클래스 라고 볼수 있는 생성자 함수를 통해 인스턴스를 생성할경우 모든 인스턴스가 공통적으로 물려받을수 있는
코드를 생성할수 있게 도와준다.
prototype는 생성자 함수를 정의할때 자동적으로 생성되는 특별한 객체로서 클래스 전체에 의마가 있는 프로퍼티와 값을 담을수 있다.
prototype이 만들어 지는 과정을 살펴 보면은
생성자 함수가 만들어지면 인터프리터는 자동으로 그 생성자 함수에 프로토타입 프로퍼티를 추가 하게된다.
그리고 그 prototype 프로퍼티에는 범용 객체를 넣어 두게 된다.
이렇게 prototype 객체에 들어가는 프로퍼티와 메서드는 그 생성자 함수로 정의된 클래스의 모든 인스턴스에 그대로 상속되게 된다.
그런데 프로토타입 객체라고 했다고 또 프로퍼티라고도 했는데 이것은 같은 의미가 된다.
해당 클래스의 prototype 객체에 추가된 프로퍼티들은 모든 인스턴스에 그대로 반영되게 해준다.
인스턴스가 프로퍼티를 가지고 있지 않아도 생성자함수의 prototype 객체에 들어있는 프로퍼티를 인스턴스가 가지고 있는 것처럼 처리하게 된다.
생성자함수의 모든 객체에 상속될 메서드나 프로퍼티를 만들려면 그 생성자 함수에 정의된 자동으로 미리 만들어진 프로토타입 객체에 속성으로 추가하면 된다.
만일 인스턴스에서 프로퍼티나 메서드를 찾으라고 하면 인터프리터는 기본적으로 인스턴스 차원(instance level) 에서 먼저 프로퍼티나 메서드를 찾게 된다.
그 곳에 프로퍼티나 메서드가 없으면 다음은 해당 인스턴스의 원래 클래스 즉 생성자 함수의 prototype 객체에서 프로퍼티나 메서드를 찾게 되는 것이다.
만일 상속된 클래스 즉 생성자함수 였다면 그 위의 슈퍼클래스(생성자 함수)의 prototype 객체를 다시 찾아 계속 거슬러 올라가게 되는 것이다.
자식 클래스(생성자 함수)의 인스턴스는 자신을 생성해준 엄마 클래스(생성자함수)의 prototype 으로부터 메서드와 프로퍼티를 상속받게 되는 것이다.

  //인스턴스 마다 동일한 특징을 가질 수 있게 생성자 함수를 만드는 예 
Cs = function(name){
  this.name = name;
  this.Nick = 'daejang'
}
Cs.prototype.Nature = 'Pure';
inst_Cs = new Cs ('나이스가이');
inst_Cs2 = new Cs ('나이스가이2');

alert(inst_Cs.name);    //1
alert(inst_Cs.Nature);  //2
alert(inst_Cs.Nick);    //3
alert(inst_Cs2.name);    //4
alert(inst_Cs2.Nature);  //5
alert(inst_Cs2.Nick);    //6
constructor
  • 프로토타입 객체의 constructor 프로퍼티

생성자 함수의 prototype 객체가 만들어지고 나면 인터프리터에서는 그 객체에 자동으로 constructor 라는 특별한 프로퍼티를 추가 하게 된다.
Constructor 프로퍼티는 프로토타입의 클래스 생성자 함수에 대한 레퍼런스이다.

Cs = function(name){
this.name = name;
this.Nick = 'daejang'
}
Cs.prototype.Nature = 'Pure';
inst_Cs = new Cs ('나이스가이');
inst_Cs2 = new Cs ('나이스가이2');

alert(Cs);
alert(Cs.prototype.constructor);
alert(inst_Cs.Nature);
alert(inst_Cs.prototype.Nature);
alert(inst_Cs.prototype.constructor);  // ????
alert(inst_Cs2.prototype.constructor); // ????

일반적으로 책에 나와 있는 constructor 에 대한 설명은 위의 범주를 벗어 나지 못한다. 하지만 저 설명을 그대로 믿고 코드를 타이핑해 보면 잘못된 결과에
직면 하게 된다. 즉 에러가 나온다는 말이다.

위 내용은 어떻게 받아 들여야 하는 것일까??
위 코드는 ECMA 표준에서 부터 출발 해야 이해할수 있는 코드이다.
5챕터를 설명 하면서 상단에 다음과 같은 내용을 기술 하였었다.

"가장 기본적으로 prototype, _proto_, constructor.prototype ,constructor 이 4가지에 대해 살펴 봅시다."

이걸 살표 봐야할 ?가 되었다.

다시 한번 생성자 함수로 부터 객체가 생성되는 순간을 살펴 보자.

inst_Cs = new Cs ('나이스가이');
inst_Cs2 = new Cs ('나이스가이2');

new 키워드를 이용해여 만들어진 객체 inst_Cs 과 inst_Cs2 는 책에서 말하는거처럼 prototype 프로퍼티를 가지고 있지 않는다.
그럼 대체 어떻게 상속이 이루어 지는 것일까?

예전 번전 그러니까 ECMA 이전 버전에서는(물론지금도 있다 파이어폭스에서만...) _proto_ 가 존재 했었다.
이 프로퍼티의 특징은 생성자 함수를 new 키워드를 통해 생성했을때
생성자 함수의 prototype 객체를 내부적으로 참조 한다.
그리고 만들어진 객체는 prototype를 참조하고 있는 _proto_ 의 프로퍼티들 을 객체 자신의 프로퍼티로 가지고 온다.
( 객체명._proto_.프로퍼티명 이렇게 접근 하던걸 객체명.프로퍼티명 이렇게 접근 하게 한다)
즉 방향성으로 보았을때 prototype의 내용을 바로 가지고 오는것이 아니라 _proto_를 통해서 상속구조를 완성 하는 것이다.
3줄로 설명 하는 가운데 어디에도 객체가 prototype 프로퍼티를 가지고 있다는 말을 쓰지 않았다.
즉 new로 만들어진 객체에는 prototype 프로퍼티를 직접 프로퍼티 명으로 지칭 하는 일은 없다 내부적으로 _proto_를 통해서
객체 자신의 프로퍼티로 가지고 오고 있을 뿐이다.

한마디로 줄이면
new 로 생성된 객체는 prototype 프로퍼티가 없고 _proto_ 혹은 constructor.prototype 을 통해서 접근이 가능하다.
constructor.prototype 은 밑에서 살펴볼 것이다.

참조 * 자바스크립트의 모든객체(생성자를 포함해서 new 로 생성된 모든 객체)는
constructor.prototype 이라고 (혹자는 이걸보고 프로토 타입 이라고도 한다. 이건 _proto_ 와 같은 표현이다.) 하는
또다른 객체를 내부적으로 참조하고 있다. 그리고 객체는 constructor.prototype의 프로퍼티들을 자신의 프로퍼티로 가지고 온다.
다시 말해 자바스크립트의 객체는 자신의 constructor.prototype에 있는 프로퍼티들을 상속받는다.
빈객체가 생성이 되면, new 연산자는 해당 객체의 constructor.prototype(_proto_와 동일한 의미이며 혹자들이 프로토타입이라고 말하는 거와 같다)
을 설정한다. 이때 생서된 객체는, 자신을 만들어낸 생성자의 prototype 프로퍼티 값을 자신의 프로토타입(constructor.prototype)으로 설정한다.
모든 함수(생성자 함수)에는 prototype 프로퍼티가 있는데, 이것은 함수가 정의될때 부터 자동적으로 생성되고 초기화 된다.
prototype 프로퍼티의 초기값은 프로퍼티가 하나있는 객체로 지정된다.
이 한개의 프로퍼티가 바로 constructor 이다. 그리고 이 constructor 프로퍼티는 prototype 프로퍼티가 연관되어 있는 생성자 함수를 가르킨다.
이러한 연관관계로 인해서 모든 객체에 constructor 프로퍼티가 존재하는 이유가 설명이 된다.
그리고 바로 이러한 이유로 인해 개발자가 생성자 함수의 prototype 프로퍼티에 추가한 프로퍼티들이 생성자를 이용하여 초기화한 모든 객체의
프로퍼티에 상속 되는 이유다.
*
라고 The Definitive Guide 5/E
202 페이지에 나와 있다. 이게 이해 되면 모든 설명은 필요없다.
이부분이 바로 자바스크립트의 진리요 생명의 말씀이다.

그렇다면 다음을 살펴 보자.

Cs = function(name){
this.name = name;
this.Nick = 'daejang'
}
Cs.prototype.Nature = 'Pure';
inst_Cs = new Cs ('나이스가이');
inst_Cs2 = new Cs ('나이스가이2');

alert(inst_Cs.name);    //1
alert(inst_Cs.Nature);  //2
alert(inst_Cs.Nick);    //3
alert(Cs);              //4
alert(Cs.prototype.constructor); //5
alert(inst_Cs);                  //6
alert(inst_Cs.Nature);           //7

Cs.prototype.Nature = 'Pure2';   //8
alert(inst_Cs.Nature);           //9
alert(Cs.__proto__);             //10
alert(inst_Cs.__proto__);        //11
alert(inst_Cs.constructor.prototype); //12
alert(inst_Cs.constructor.prototype.constructor); //13
alert(inst_Cs.constructor); //14
inst_Cs.constructor.prototype.Nature = 'Pure2'    //15
alert(inst_Cs2.constructor.prototype.Nature);     //16
alert(inst_Cs2.constructor.prototype.toString);   //17
alert(inst_Cs2.toString);                         //18
alert(Cs.toString);                               //19
alert(Cs.prototype.toString);                     //20

이 예제는 모든것을 보여 주고 있다. 내부가 어떻게 돌아가고 있는지를 말이다.

아까 _proto_를 이야기 하며 파이어 폭스에서만 쓰고 있다고 하였다. 이말은 표준이 아니라는 말과 같지 않는가??
그렇다 이건 이제 표준이 아니다 현제 IE 와 파이어폭스 객체명._proto_ 를 대체 해서 쓸수 있는건 객체명.constructor.prototype 이다.

위코드를 살펴 보면 재미 있는 내용이 많이 있다.
첫번째로 4번 과 5번 13번 14번 이 왜 같은 결과를 리턴 하는 것일까?

방금전에 설명 했던 내용을 적용 하면 바로 설명이 가능 하다.
모든 생성자 함수 와 함수에는 prototype 이라는 프로퍼티가 있는데 이것은 함수가 정의될때 자동적으로 생성되고 초기화 된다.
이때 새로 생성된 객체는 자신을 만든 생성자 함수의 prototype를 객체 자신의 _proto_ 를 통해서 레퍼런스 한다음에
객체 자신의 _proto_ 의 프로퍼티를 자신의 프로퍼티로 설정한다.
( 객체명._proto_.프로퍼티명 이렇게 접근 하던걸 객체명.프로퍼티명 이렇게 접근 하게 한다)
이렇게 참조가 전달 되는 과정에서 맨처음 생성자 함수가 정의 될대 함수에 prototype 프로퍼티가 자동으로 생성되고 초기화 되듯이
prototype또한 자기 자신의 프로퍼티로 constructor프로퍼티를 가지게 된다.
이 constructor 프로퍼티는 prototype이 연관되어 있는 생성자 함수를 가르킨다.
이러한 이유로 객체가 생성될때 prototype이 가지고 있는 프로퍼티가 _proto_ 로 참조 되면서
constructor 역시 _proto_ 에 참조되어 진다.

그래서 위에 설명 한대로
객체명._proto_.프로퍼티명 -> 객체명.프로퍼티명 이렇게 접근 하는걸 풀어서 보면
inst_Cs._proto_.constructor 이렇게 접근 하는것과 inst_Cs.constructor , Cs.prototype.constructor
접근 하는게 같은 결과를 나타낼수 있는 것이다.

지금껏 설명해온 특성으로 인해 상속 구조를 js에서 구현 할수 있는 것이다.

특정 인스턴스나 객체에서 부모에서 상속된 프로퍼티나 메서드를 새롭게 만들려면 (overriding) 하려면 같은 이름의 프로퍼티나 메서드를 다시 정의하면 된다.
달 리 말하면 자식 객체나 인스턴스에서 부모에서 상속된 프로퍼티나 메서드와 동일한 이름이지만 기능이 다른 것을 만들고 싶다면 단순히 이름만
같이 사용해서 새롭게 정의하면 해당 프로퍼티나 메서드는 엄마와의 상속 고리가 끊어지게 된다는 것이다.
검색을 하는 순서상 인터프리터는 해당 인스턴스에서 해당 프로퍼티나 메서드가 있으면 더 이상 부모까지 찾아가지 않게 된다.
다음에는 그것을 (프로퍼티의 검색 순위) 확인하여 보자

각 프로퍼티의 검색 순위

클래스 즉 생성자 함수 차원의 프로퍼티와 인스턴스 즉 객체의 프로퍼티는 서로 다르다.
(prototype으로 확인 가능하다. 생성자 함수의 prototype은 해당생성자의 인스턴스에선 prototype로 접근 하지 못하고
_proto_ 혹은 constructor.prototype 으로 접근해야 한다.)

정확히 말하면 prototype chain 이라는 상속 처럼 보이는 과정에 얽히게 되어 있을 뿐이지 서로 다른 프로퍼티 라는 의미다.
동일한 물체를 가르키는 A와 B가 있다고 할때 두 A와 B가 같은걸 가르킨 다고 해서 같은건 아니지 않는가? 엄연히 껍대기는 다른거다.(내용은 같을지라도)
말장난은 이쯤에서 그만 두도록 하고 소스를 통해서 살펴 보도록 하자

인스턴스 차원의 프로퍼티라는 것은 클래스를 통해 인스턴스를 생성한 당시나 이후에 변경된 인스턴스의 프로퍼티를 말한다.
앞에서 말씀 드린 것처럼 인터프리터는 다음 순서로 프로퍼티를 찾게 된다.

1.instance level : 먼저 해당 인스턴스에 해당 프로퍼티가 있는지 인스턴스 차원에서 확인한다.
2.instance level (지역변수로 정의된 것) : 인스턴스를 만든 클래스의 원 코드에 정의된 프로퍼티가 있는지 확인하다.
3.prototype level : prototype 객체에서 프로퍼티를 찾게 된다.

그림 코드 02
    var Cs = function(){
    this.Nick = 'daejang (instance level (지역변수로 정의된 것) property)' // 2
    }
    Cs.prototype.Nick = 'daejang (prototype level property)'; // 3
    inst_Cs = new Cs ();
    inst_Cs.Nick = 'daejang (instance level property)'; // 1
    alert(inst_Cs.Nick);

결과는 당여히 예측 하였겠지만 daejang (instance level property) 이다
위 코드들 파이어폭스의 파이어 버그를 이용해서 메모리에 올라온 내용을 살펴보자.

그림 코드 03
    var Cs = function(){
    this.Nick = 'daejang (instance level (지역변수로 정의된 것) property) 2' // ?
    }
    Cs.prototype.Nick = 'daejang (prototype level property)'; // ?
    inst_Cs = new Cs ();
    alert(inst_Cs.Nick);

어떤결과가 나올지 직접 테스트 해보고 파이어버그를 통해 값을 확인해 보자.

그림 코드 04
    var Cs = function(){
    this.Nick = 'daejang (instance level (지역변수로 정의된 것) property)' // #2
    }
    Cs.prototype.Nick = 'daejang (prototype level property)'; // #3
    inst_Cs = new Cs ();
    inst_Cs.Nick = 'daejang (instance level property)'; // #1

    delete inst_Cs.Nick;
    alert(inst_Cs.Nick);

delete 연산자를 통해서 프로퍼티를 완전히 삭제하는걸 보여준다. 위에서 설명할때 껍데기 운운 했던 기억이 있을것이다
그때 그이야기를 왜 했었는지 이예제를 통해서 보여주려고 한다.
일단 결과를 확인해 봐야 한다. 어떤 결과가 출력될까? 결과는 "daejang (instance level (지역변수로 정의된 것) property)" 출력된다.
출력된 내용이야 별로 중요하지 않으니 어?서 저런 결과과 나왔는지 과정이 중요하다. 그걸 살펴보자.
아까 아래와 같이 설명 한적이 있을것이다
순서.
1.instance level : 먼저 해당 인스턴스에 해당 프로퍼티가 있는지 인스턴스 차원에서 확인한다.
2.instance level (지역변수로 정의된 것) : 인스턴스를 만든 클래스의 원 코드에 정의된 프로퍼티가 있는지 확인하다.
3.prototype level : prototype 객체에서 프로퍼티를 찾게 된다.

코드의 흐름을 따라가 보자

그림에서 12라인을 보면 해당 시점에서 메모레 영역에 올라간 상태를 살펴보면 아래와 같다.

  • inst_Cs - Object
    • Nick - "daejang (instance level (지역변수로 정의된 것) property)"
  • Cs - function()
    • prototype - Object Nick=daejang (prototype level property)
      • Nick - "daejang (prototype level property)"

여기서에서 14라인의 delete 키워드를 만나면 다음과 같이 된다.

  • inst_Cs - Object Nick=daejang (instance level property)
    • Nick - "daejang (instance level property)"
  • Cs - function()
    • prototype - Object Nick=daejang (prototype level property)
      • Nick - "daejang (prototype level property)"

그리고 delete 문을 모두 종료 하고 나면 다음과 같은 메모리맵을 가지게 된다.

  • inst_Cs - Object Nick=daejang (prototype level property)
    • Nick - "daejang (prototype level property)"
  • Cs - function()
    • prototype - Object Nick=daejang (prototype level property)
      • Nick - "daejang (prototype level property)"

결과를 보자면 instance level의 프로퍼티를 제거하면 Instance level (지역변수로 정의된 것) 의 프로퍼티가 함께 제거되는 것을 알 수 있다.
그리고 Instance level 프로퍼티를 해당 인스턴스에서 제거하면 프로토타입 차원의 동일명의 프로퍼티 값을 검색해 준다.

파이어버그를 통해서 중단점을 inst_Cs.Nick = 'daejang (instance level property)'; 부분에 걸고 한행씩 실행해 보면
눈으로 직접 확인해 확신을 가질수 있을것이다.

지금까지 내용으로 알수있는 사실

자바스크립트에서의 상속은 인터프리터에 의해서 프로퍼티를 찾는 과정의 일부로서 자동으로 발생한다.
이러한 특성 때문에 예기치 못한 문제가 발생 하기도 한다.
다음 코드를 살펴 보자

 
var Cs = function(name){
}
inst_Cs1 = new Cs ('DragonBall');
alert(inst_Cs1.name); // 1
Cs.prototype.name = 'Daejang (prototype level property)';
inst_Cs2 = new Cs ('DrgonFly');
alert(inst_Cs1.name); // 2
alert(inst_Cs2.name);

이미 생성된 인스턴스도 뒤에 추가된 프로토타입 프로퍼티의 영향을 받을까 ?

답은 그렇다 이다. 자바스크립트는 인터프리터 언어이다.
그렇기 때문에 코드를 읽어 들이는 순간 순간 마다 처리가 이루어 진다.
위에서 살펴본 코드 처럼 이미 객체가 생기고 난 다음 이더라도....
다음 라인에서 코드를 읽어 들일때 새로운 prototype 프로퍼티가 추가 되면
그다음 라인에서 읽어 들일 inst_Cs1 와 inst_Cs2 의 입장에서는 조건으로 주어진 프로퍼티를 자신의 객체에서 찾고
없을경우 생성자 함수의 prototype을 뒤지게 된다.

객체지향

상속

어떤 클래스를 슈퍼클래스로 만드는 것은 어떤 클래스를 서브클래스로 만드는 것과 같은 말이 된다.
어쨌든 이런 서브 및 슈퍼클래스관계를 맺어주게 하려면 자바와 같은 다른 OOP언어에서는 extends 키워드를 사용하지만
자바스크립트에서는 다음과 같은 syntax 를 사용하면 된다.

서브클래스명(생성자 함수).prototype = new 슈퍼클래스명();

그림 코드 05
var Person = function(){ // #1
}
Person.prototype.nature = 'Pure';

var Soldier = function(branch){ // #2
  this.branch = branch;
}

alert(Soldier);                       // #3
alert(Soldier.prototype.constructor); // #4

Soldier.prototype = new Person();    // #5
Soldier.prototype.title = 'Captain';

alert(Soldier);                       // #7
alert(Soldier.prototype.constructor); // #8


Soldier.prototype.constructor = Soldier; // #8

alert(Soldier);                         // #9
alert(Soldier.prototype.constructor);   // #10


Inst_Soldier1 = new Soldier('Army');
Inst_Soldier2 = new Soldier('Force');
alert(Inst_Soldier1.branch); // #11
alert(Inst_Soldier2.branch); // #12
alert(Inst_Soldier1.title); // #13
alert(Inst_Soldier2.title); // #14
alert(Inst_Soldier1.nature); // #15
alert(Inst_Soldier2.nature); // #16

생성자 체인 : 상위 생성자 함수의 생성자 함수를 명시적으로 호출 하는 행위 ( Soldier.prototype = new Person() )

지금까지 내용으로 알수있는 사실

위에서 밝혔다 싶이 construcor은 생성자를 레퍼런스 하는 prototype 의 프로퍼티 이다.
그래서 생성자 체인 을 거는 5를 전후로 생성자의 값이 서로 다른것을 확인 할수 있다(직접 처서 눈으로 확인해 보라)
위코드에서는 굳이 #8 라인 처럼 constructor 의 생성자를 재지정 해주는 이유가 들어나 있지 않지만
조금 뒤에 코드를 통해 저렇게 처리한 이유를 살표보자

결론적으로 위 코드를 통해 알수있는 내용은 부모 자식 구조의 상속 구조를 prototype의 생성자 체인 을 통해 구현 할수 있다는 내용과
생성자함수.prototype.constructor 는 자동으로 생성되면 동시에 자기 자신을 가르키고 있다는 점이다.

// 사격형 과 정사각형의 상속 관계
function Rectangle(w, h) {
  this.width  = w;
  this.height = h;
}

Rectangle.prototype.area = function() { return this.width * this.height ; }

function PositionedRectangle(x, y, w, h) {
 Rectangle.call(this, w, h);
 this.x = x;
 this.y = y;
}

PositionedRectangle.prototype = new Rectangle();

delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;

PositionedRectangle.prototype.constructor = PositionedRectangle;

PositionedRectangle.prototype.contains = function(x, y) {
 return ( x >this.x && x<this.x + this.width && 
          y >this.y && y<this.y + this.height);
}


var r = new PositionedRectangle(2,2,2,2);
alert(r.x + ',' + r.y + ',' + r.width + ',' + r.height);
alert(r.contains(3,3));
alert(r.area());

Rectangle.call(this, w, h)PositionedRectangle.prototype = new Rectangle() 이 행위를 통해서 생성자 체인을 보여주었는데.
이걸 좀더 간소화한 다음 구문을 보자

//변경된 코드
function Rectangle(w, h) {
  this.width  = w;
  this.height = h;
}

Rectangle.prototype.area = function() { return this.width * this.height ; }
PositionedRectangle.prototype.superclass = Rectangle;

function PositionedRectangle(x, y, w, h) {
 this.superclass(w, h);
 this.x = x;
 this.y = y;
}

위와 같은 처리는 더이상 생성자 체인은 구현하기 위해 call 이나 apply 를 사용할 필요가 없음을 보여준다.
(결과적으로 코드의 직관성을 높여주고 좀더 이해하기 쉽게 해준다)
허나 테스트 해보면 알겠지만.. prototype된 부모 생성자 함수에 대한 처리를 따로 해야 한다.

생성자 체인에 의한 상속의 특이점

+*서브클래스를 만든 슈퍼클래스(부모 클래스)의 프로토타입 프로퍼티를 변경하면 ?

 
var Person = function(){
}
Person.prototype.nature = 'Pure';
var Soldier = function(branch){
this.branch = branch;
}
Soldier.prototype = new Person(); // #1
Soldier.prototype.title = 'Captain';
Inst_Soldier1 = new Soldier('Army');
Inst_Soldier2 = new Soldier('Force');

Person.prototype.nature = 'Very Pure'; // #2
alert(Inst_Soldier1.branch);
alert(Inst_Soldier2.branch);
alert(Inst_Soldier1.title);
alert(Inst_Soldier2.title);
alert(Inst_Soldier1.nature); // #3
alert(Inst_Soldier2.nature); // #3

이미 생성된 인스턴스도 뒤에 추가된 프로토타입 프로퍼티의 영향을 받을까 ?

답은 그렇다 이다. 자바스크립트는 인터프리터 언어이다.
그렇기 때문에 코드를 읽어 들이는 순간 순간 마다 처리가 이루어 진다.
위에서 살펴본 코드 처럼 이미 객체가 생기고 난 다음 이더라도....
다음 라인에서 코드를 읽어 들일때 새로운 prototype 프로퍼티가 추가 되면
그다음 라인에서 읽어 들일 Inst_Soldier1 와 Inst_Soldier2 의 입장에서는 조건으로 주어진 프로퍼티를 자신의 객체에서 찾고
없을경우 생성자 함수의 prototype을 뒤지게 된다.

  • 1. 서브클래스의 인스턴스(instance level)에서 프로퍼티를 찾는다.
  • 2. 그곳에 없었으면 다시 서브클래스의 프로토타입(prototype level)에서 프로퍼티를 찾는다.
  • 3. 역시 못 찾으면 슈퍼클래스(엄마 클래스)의 프로토타입(prototype level)에서 다시 프로퍼티를 찾는다.

_proto_ 프로퍼티

이미 앞에서 여러번에 걸쳐 강조 하였지만 너무 나도 중요하기에 따로 분리해서 다시 한번 설명 하려 한다.
_proto_ 의 정의는 다음과 같다.

  • 어떤 객체가 만들어지든(new XXX()) , 인터프리터는 자동으로 _proto_ 프로퍼티를 할당해 주게 된다.
  • _proto_ 프로퍼티는 그 객체의 생성자 함수의 prototype 프로퍼티에 대한 레퍼런스이다.
  • 따라서 모든 클래스의 prototype 객체와 그 클래스에서 생성된 인스턴스는 _proto_ 프로퍼티와 값을 가지고 있다 .
  • _proto_ 프로퍼티 값은 인스턴스 자신이나 자신을 생성했던 클래스 안에서 원하는 프로퍼티를 찾을 수 없을 때 해당 프로퍼티를 어디에서 찾아야 하는지의 장소를 나타낸다.
09.html 10.html 11.html
var Cs = function(name){
this.name = name;
this.Nick = 'daejang'
}
Cs.prototype.Nature = 'Pure';
inst_Cs = new Cs ('나이스 가이');
alert(Cs);
alert(Cs.prototype.constructor);
alert(Cs == Cs.prototype.constructor); // #1
alert(inst_Cs.__proto__); // #2
alert(Cs.prototype == inst_Cs.__proto__); // #3
#1 : 클래스 Cs 와 클래스 Cs.prototype.constructor 가 같은 것임을 보여주고 있다.
#2 : _proto_ 프로퍼티에는 객체가 들어가 있다는 것을 나타내고 있다.
#3 : 클래스 Cs 의 프로토타입은 인스턴스 inst_Cs 의 _proto_ 프로퍼티의 레퍼런스가 같다는 것을 보여주고 있다.
var Cs = function(name){
this.name = name;
}
Cs.prototype.A_0 = function(){
alert(this.name);
}
inst_Cs = new Cs ('나이스 가이');
inst_Cs.A_0(); // #1

#1 : 인스턴스 inst_Cs 에서 상속된 A_0 메서드를 호출하면 인터프리터는 내부적으로 inst_Cs._proto_ 프로퍼티를 통해 Cs.prototype.A_0 메서드를 호출하게 된다.

var Cs = function(name){
this.name = name;
}
Cs.prototype.A_0 = function(){
alert(this.name);
}
inst_Cs = new Cs ('나이스 가이');
alert(inst_Cs.__proto__ == Cs.prototype); // #1

#1 : 인스턴스 inst_Cs 가 클래스 Cs 에서
상속된 인스턴스임을 보여주고 있다.

12.html
var Person1 = function(){
}
Person1.prototype.nature = 'Pure';
var Person2 = function(){
}
Person2.prototype.nature = 'Very Pure';
var Soldier = function(name, branch){
this.name = name;
this.branch = branch;
}
Soldier.prototype = new Person1();
Soldier.prototype.title = 'Captain';
var Nurse = function(gender, ward){
this.gender = gender;
this.ward = ward;
}
Nurse.prototype = new Person2();
Nurse.prototype.title = 'Sir';
Inst_Soldier1 = new Soldier('나이스가이', 'Army');
alert(Inst_Soldier1.branch);
alert(Inst_Soldier1.name);
alert(Inst_Soldier1.title);
alert(Inst_Soldier1.nature);
Inst_Soldier1.__proto__ = Nurse.prototype; // #1
alert(Inst_Soldier1.branch); // #2
alert(Inst_Soldier1.name); // #2
alert(Inst_Soldier1.gender); // #3
alert(Inst_Soldier1.ward); // #3
alert(Inst_Soldier1.title); // #4
alert(Inst_Soldier1.nature); // #5

#1 : 인스턴스 생성후 Nurse 클래스의 프로토타입 객체의 프로퍼티를 이용하도록 상속 링크를 걸어준 것이다.
#2 : 인스턴스 차원의 프로퍼티는 바뀐 것이 아니므로 여전히 Soldier 객체의 인스턴스 차원의 프로퍼티와 값을 사용하고 있다.
#3 : 일단 인스턴스 생성 후에 상속 링크가 변경되었으므로 생성 당시 전달된 인자에 의한 프로퍼티는 변경되지 않는다.
gender 와 ward 는 Nurse 클래스의 인스턴스 차원의 프로퍼티이므로 Soldier 인스턴스에서는 해당 프로퍼티를 찾을 수 없다고 undefined 를 나타낸다.
#4 : 상속 링크를 변경시킨 결과로 Nusre 클래스의 프로토타입 객체의 프로퍼티 값을 보여준다.
#5 : Nurse 클래스의 슈퍼클래스의 프로트타입 객체를 그대로 물려 받으므로 차이가 없다.
위의 코드에서 볼 때, Inst_Soldir1 인스턴스는 prototype 객체에서 상속된 것이므로,
Inst_Soldier1._proto_ 는 Soldier.prototype 객체가 된다.
또한 Soldier 클래스는 Person 클래스에서 상속된 것이므로,
Soldier.prototype_proto_ 는 Person.prototype 이 된다
결국 서브클래스의 prototype 은 슈퍼클래스의 prototype 객체에서 물려 받게 되는 것이다.
지금까지 우리는 _proto_ 프로퍼티의 값을 설정해 주지 않았다.
_proto_ 프로퍼티는 인스턴스나 클래스가 만들어 질 때 자동으로 값이 설정된 것이다.
물론 필요에 따라 이_proto_ 프로퍼티의 값을 바꿀 수도 있다.
아래처럼 _proto_ 프로퍼티의 값을 변경하게 되면
Inst_Soldier1._proto_ = Nurse.prototype;
처음 인스턴스를 생성했던 Soldier 클래스에서 상속 고리가 바뀌게 되는 것이다.
즉, Nusrse 클래스의 인스턴스로 인터프리터 내부에서는 간주하게 된다.
이렇게 되면 Soldier 클래스의 프로토타입 객체를 통한 상속된 프로퍼티들은 제거(wiped clean)되게 된다.

13.html

var Person = function(name, id){ // #1
}
Person.prototype.nature = 'Pure';
Person.prototype.A_1 = A_0;
function A_0(){
alert('My name is ' + this.name);
alert('My ID is ' + this.id);
alert('My nature is ' + this.nature);
}
var Soldier = function(name, id){
this.name = name;
this.id = id;
}
Soldier.prototype .__proto__ = Person.prototype; // #2
var Nurse = function(name, id){
this.name = name;
this.id = id;
}
Nurse.prototype.__proto__ = Person.prototype; // #2
Inst_Soldier = new Soldier('Yong Min Lee', 'daejang');
Inst_Nurse = new Nurse('Min Ha Lee', 'minhalee');
Inst_Nurse.nature = 'Very Pure';
Inst_Soldier.A_1(); // #3
Inst_Nurse.A_1(); // #3

#1 : Person 클래스는 superclass 가 될 것이다.
Person = function(name, id){ 을
Person = function(){ 로 바꾸어도 차이가 없다.
#2 : Soldeir 클래스와 Nurse 클래스 모두 동일하게 Person 클래스에서 상속된 것을 나타내고 있다.
주의해서 보아야 할 것은, 이전의 예제들에서 보았던 상속 방법과 다른 방법을 사용하고 있다는 것을 알 수 있다.
여기서 _proto_ 는 인스턴스에게 어디서 prototype 객체를 찾아야 할지 가르쳐 주는 역할을 한다.
문 문장을 이해하기 쉽게 읽으면,
'Soldier 클래스(혹은 Nurse 클래스)의 프로퍼티는 오른쪽의 Person 클래스의 프로퍼티에서 상속되었다' 가 된다.
물론 이렇게 상속된 상태에서도 Soldier 클래스(혹은 Nurse 클래스)에 또 다시 프로퍼티를 추가할 수 있게 된다.
예) Soldier.prototype.추가될 프로퍼티명 = 값;
그리고 만일 이 Soldier 클래스(혹은 Nurse 클래스)를 또 다른 클래스에 상속시키게 되면 역시
새로 추가된 것과 엄마(superclass) 에서 물려받은 프로퍼티 모두를 상속시켜줄 수 있게 된다.
#3 : Inst_Soldier 나 Inst_Nurse 에서 A_1 메소드를 호출하게 되면 다음과 같은 과정을 거치게 된다.
먼저 해당 인스턴스에 A_1 메서드를 찾아 보게 된다.
당연히 해당 인스턴스에 없으므로 자신을 만든 클래스에서 다시 찾게 된다.
물론 그곳에도 없으니 사다리 타기를 계속해서 엄마인 super class 즉, Person 으로 올라가게 된다.
그곳의 prototype 객체의 프로퍼티인 A_1 을 찾게되면 다시 이에 링크된 A_0 을 찾아 호출하게 된다.
이렇게 사다리를 타서 함수를 찾아 호출하게 되면 다시 함수는 해당 인스턴스에서부터
this.name,
this.id,
this.nature
를 찾게 된다.

문서에 대하여

문서정보

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