'prototype'에 해당되는 글 2건

본 내용은 Javascript The Definition Guide 5/E의 내용을 발췌, 요약한 것으로 부분적으로 제가 이해하기 쉽게 적은 커맨트들이 있습니다^^;

자바스트립트에서는 Java, Ruby, C++등의 객체지향 언어에서와 같이 클래스를 지원하지는 않지만(javascript 2.0에서는 클래스가 지원된다고 합니다), 자바스크립티의 객체에는 고유의 프로퍼티집합을 가지고 있는데, 이를 이용해서 모조 클래스(pseudoclass,이하 클래스)를 만들 수 있다고 한다. 이 클래스는 프로토타입 객체생성자 함수를 사용하여 구현할 수 있다고 한다.

생성자

  1. new Obejct();
  2. var array = new Array(10);

new를 사용하는 방식은 아무 프로퍼티도 없는 객체 생성 후에 new 뒤에 있는 함수를 호출하게 되는데 이 함수 안의 this를 새로 생성된 객체가 가리키게 된다.

  1. function Rectangle(w, h){
  2. this.width = w;

  3. this.height = h;

  4. }
  5. var rect1 = new Rectangle(2, 4);
  6. var rect2 = new Rectangle(8.5, 11);

생성자 함수는 일반적인 객체지향에서 그렇듯이 반환값(return)값이 없다.

프로토타입과 상속

자바스크립트에서 메소드는 객체의 프로퍼티이자 호출 가능한 함수이다. 메소드 내의 this는 그 메소드를 소유하고 있는 객체를 참조한다. 객체지향적으로 구현을 한다면?

  1. var r = new Rectangle(8.5, 11);
  2. r.area = function(){ return this.width * this.height; }
  3. var a = r.area;

하지만, 이는 객체 r을 위한 메소드일 뿐 클래스 Rectangle이 가지고 있는 메소드가 아니기 때문에, 객체향적으로 한다면 다음과 같이 생성자 안에 그 함수를 선언하는 것이 더 맞겠다.

  1. function Rectangle(w, h){
  2. this.width = w;
  3. this.hegith = h;
  4. this.area = function(){ return this.width * this.height; }
  5. }

하지만, 이게 제일 좋은 해결책은 아니라는 것. 이렇게 할경우 객체 3개를 생성하면 3개의 area함수가 각각의 객체에 있기때문에 비효율 적이다. 단지 width와 height가 다를뿐 그 안의 연산은 동일하기 때문.

이를 해결하기 위한 것이 자바스크립트의 모든 객체가 가지고 있는 프로토타입이라는 또 다른 객체를 내부적으로 참조할 수 있다는 것을 이용하는 것이다. 객체는 프로토타입의 프로퍼티들을 자신의 프로퍼티로 가져온다. 즉, 자바스크립트 객체는 자신의 프로토타입에 있는 프로퍼티들을 상속 받는다. 프로토타입에는 기본적으로 constructor라는 프로퍼티를 가지는데 이는 프로토타입이 연관되어 있는 생성자 함수를 가리킨다.

  1. var d = new Date();
  2. d.constructor == Date ; // true 반환
  3. var o = new Objec();
  4. typeof o == "object"; //true 반환 typeof는 constructor프로퍼티 값을 사용한다.

프로토타입 프로퍼티를 이용한다면

  1. //생성자 함수는 각 인스턴스의 프로퍼티가 다른 값이 되도록 초기화시킨다.
  2. function Rectangle(w, h){
  3. this.width = w;
  4. this.hegith = h;
  5. }
  6. //프로토타입 개체는 각 인스턴스들이 공유해야하는 프로퍼티나 메소드를 정의한다.
  7. Rectangle.prototype.area = function(){ return this.width * this.height; }

Rectangle.prototype은 생성자 Rectangle(w, h)와 연결되며 이 생성자를 통해 생성되는 모든 객체는 프로토타입이 가진 모든 객체를 그대로 상속 받는다.

이말은 프로토타입 객체가 메서드나 상수같은 프로퍼티를 위치시키기 좋은 장소임을 의미한다. 프로토타입 객체를 사용함으로서

  • 각 객체가 프로토타입의 프로퍼티를 상속받기 때문에 이들이 필요로 하는 메모리를 절약 해주며
  • 프로토타입에 새로운 프로퍼티가 생성되면, 이미 생성된 객체에도 이 프로퍼티를 그대로 상속받는다
상속받은 프로퍼티의 읽기와 쓰기

모든 객체가 가지고 있는 프로토타입이 상속을 가능하게 해주는 원리가 무엇일까? 그것은,

객체 o의 프로퍼티 p를 읽는다고 한다면, o에 p라는 프로퍼티가 있는지 검사 후에 없을 경우 o.prototype에 p가 있는지 검사하는 식이다.

자바스크립트는 기본적으로 각 객체에서 프로타입의 프로퍼티를 쓰지 못하게끔 한다. 그것을 가능하게 한다면 부모클래스의 프로퍼티를 자식클래스의 객체가 바꾸어버려 결국 모든 객체의 프로퍼티를 바꿔버리게 하는 최악의 결과를 만들 수 있기 때문이다. 따라서

프로퍼티 상속은 프로퍼티를 쓸 때가 아닌 읽을 때만 일어난다.

만약 객체 o가 프로토타입으로부터 상속받은 p라는 프로퍼티에 값을 넣고자 한다면, o객체의 p프로퍼티를 새로 생성하여 거기에 값을 넣는다. 그리고선 프로토타입의 p를 상속하지 않는다. 이를 "o에 p프로퍼티가 프로토타입의 p프로퍼티를 가렸다(shadow)혹은 숨겼다(hides)라고한다."

내장형타입의 확장

사용자 정의 클래스에는 프로토타입 객체가 존재하지 않는다. 내장형(built-in)클래스에는 모두 프로토타입객체가 존재하며, 이로서 built-in클래스에 새로운 메소드를 정의할 수 있다.

  1. String.prototyp.showLength = function(){
  2. alert(this.length);
  3. }
  4. var msg = "hello";
  5. msg.showLength();

이것이 바로 내장형 타입의 확장이다. 하지만 이러한 방식은 코드의 가독성을 해칠 수 있으므로, low-level의 자바스크립트 프레임 워크를 제공할 목적이 아니라면 내장형(built-in)타입에 재정을 하는 것은 삼가는 것이 좋다.

자바스크립트의 클래스 따라하기

자바스크립트는 '객체'라는 것을 제공하기는 하지만, '클래스'를 제대로 표현할 수 있는 방법을 제공하지는 않는다. 즉 다른 객체지향언어는 '클래스'를 통해서 객체지향을 지원하지만, 자바스크립트는 프로토타입을 통하여 객체지향을 가능케 한다.

자바스크립트에서는 클래스의 이름을 만들때 첫글자가 대문자로, 객체는 첫글자가 소문자로하는 것이 일반적이다.

자바의 클래스에는 네개의 기본타입이 있다.

  • 인스턴스 프로퍼티 : 자바스크립트에서의 프로퍼티는 기본적으로 인스턴스 프로퍼티이다. 하지만, 객체지향적인 모양새를 유지하기 위해서는 생성자에서 프로퍼티를 생성하고 값을 할당하는 것이 좋다.
  • 인스턴스 메소드 : 인스턴스메소드라고 해서 모든 객체가 이 메소드의 사본을 가지고 있다는 의미는 아니다. 자바스크립트에서의 인스턴스 메소드는 생성자의 프로토타입 객체에 정의된 함수를 의미한다. 따라서 모든 객체는 이 프로토타입에 있는 함수를 상속하여 공유한다. 인스턴스메소드에서는 호출객체나 인스턴스를 참조하기 위하여 this를 사용하는데, 이는 자바나 C++과 차이첨이 될 수 있다. 자바나 C++에서는
    return width * height;

    가 될 수 있겠지만, 자바스크립트에서는 반드시
    return this.width * this.height;
    가 되어야만 한다.  이것이 귀찮다면 다음도 가용하다.
    with(this){ return width * height;}

  • 클래스 프로퍼티 : 모든 객체가 공유하기 위해서 생성자 함수의 프로퍼티로 정의하여 사용할 수 있다.
    Rectangle.UNIT = new Rectangle(1,1);
  • 클래스 메소드 : Date.parse()가 그 예이다. 클래스메소드에서 사용되는 this는 특정 인스턴스나 객체를 참조하지 않으며, 생성자 함수 자체를 참조한다.(다만, 일반적으로 클래스 메소드는 this를 참조하지 않는다). 자바스크립트에서 클래스 메소드를 정의하기 위해서는 생성자 함수의 프로퍼티로 연결시키면 된다.
private 멤버

자바스크립트에서 encapsulation은 어떻게 할까?  자바스크립트에서는 closure를 통해서 이를 흉내낼 수 있다.

http://www.crockford.com/javascript/private.html 참고.

  1. function Rectangle(w, h){
  2. //생성자의 프로퍼티에 w,h를 대입하는 것이 아니라, w,h에 접근할 수 있는 메소드를 정의한다.
  3. this.getWidth = function(){ return w;}
  4. this.getHeight = functino(){ return h;}
  5. }
  6. Rectangle.prototype.area = function(){
  7. return this.getWidth() * this.getHeight();
  8. }

공통적인 객체 메소드

여기에서는 자바스크립트 클래스를 정의할때 유념하고 정의해야할 메소드에 대해 다룬다.

 toString()

하나의 클래스를 정의하면 그 안에는 반드시 인스턴스 메소드로서 toString()을 정의해야만 한다. 문자열을 객체로 바꾸는 parse()메소드를 정의하는 것도 생각해볼 수 있다.

  1. Rectangle.prototype.toString() = function(){
  2. return "width:" + this.getWidth() + ",height:" + this.getHeight();
  3. }
valueOf()

기본타입에 존재하는 클래스를 정의할 경우에만 정의하면 된다. 여기서 기본타입은 자바에서의 기본 변수 타입을 말한다. int, float, long, double, char, boolean 등이 그것이다.

비교 메소드

자바스크립트에서의 동등메소드는 서로 동일한 객체를 참조하고 있는지를 검사한다. 자바에서는 객체가 (그 안의 컨텐츠가) 동일한지 검사하기 위하여 메소드를 정의하는데(equals) 자바스크립트에서도 객체에서 equals 함수를 정의하는 것이 좋다. 물론 equals함수는 자신의 객체(this) 외의 객체를 인자로 받아서 비교를 하는데, 이 비교는 개발자에게 전적으로 달려있다. 객체 a와 b 각각에 존재하는 프로퍼티 p의 값이 10 이하면 동일한 것으로 간주하고 싶다고 한다면 그렇게 하면 되는 것이다. 클래스에서 비교 연산자 메소드를 정의하고 싶다면 자바에서처럼 compareTo()를 정의하면 된다.

  1. a < b | a.compareTo(b) < 0
  2. a <= b | a.compareTo(b) <= 0
  3. a > b | a.compareTo(b) > 0
  4. a >= b | a.compareTo(b) >= 0
  5. a == b | a.compareTo(b) == 0
  6. a != b | a.compareTo(b) != 0

compareTo메소드를 정의해 놓으면 이 클래스의 객체로 이루어진 배열을 정렬하고자 할때 sort매소드의 closure안에서 유용하게 사용할 수 있다.

  1. ary.sort(function(a,b){ a.compareTo(b); });
  2. // 간단하게는 Rectangle의 메소드 파라미터를 넘겨주는 식으로도 가능하다.
  3. ary.sort(Rectangle.compare);

경우에 따라서는 equals()메소드와 compareTo()메소드의 결과가 다르게 나타날 수도 있는데, 그럴 경우 찾기 힘든 버그를 발생시킬 수가 있다. 따라서 equals()메소드와 compareTo()메소드가 동일한 의미를 가지도록 부여하는 것이 좋을 것이다.

슈퍼클래스 와 서브클래스

자바스크립트에서도 프로토타입 객체를 이용하여 자바나 C++과 같이 슈퍼클래스와 서브클래스 구조를 흉내낼 수 있다.

자바스크립트에서 최상위 클래스는 Object이다. 객체는 생성자 함수의 프로토타입 객체에 있는 프로퍼티들을 상속받는 다는 것을 기억해보자. 그런데, 여기서 집고 넘어가야 할 것이 있다.

프로토타입도 객체로서 Object() 생성자를 사용하여 만들어진다. 이말인 즉, 프로토타입 객체 또한 Object.prototype 프로퍼티들을 상속받는다. 그렇다면 Rectangle클래스의 객체 r에서 프로퍼티를 찾고자 할때, 그 순서는 다음과 같이 이루어진다.

  1. 객체 r의 프로퍼티에서 찾는다
  2. Rectangle.prototype 객체의 프로퍼티에서 찾는다.
  3. Object.prototype 객체의 프로퍼티에서 찾는다.

만약 자신이 정의한 클래스의 서브클래스를 만들고 싶다면? 생성자 체이닝(chaining)을 사용 할 수 있다.

  1. function Rectangle(w,h){
  2. this.width = w;

    this.height=h;

  3. }
  4.  
  5. function SubRectangle(x,y,w,h){
  6. //width와 height를 초기화 할 수 있도록 새로운 객체의 수퍼 클래스 생성자를 호출, 초기화될 객체의 메소드로 생성자를 호출하도록 call메소드를 하용
  7. // 이 방식이 바로 생성자 체이닝(chaning)이다.
  8. Rectangle.call(this, x, y);
  9. this.x = x;
  10. this.y = y;
  11. }
  12. //상속구조를 만들기 위하여 명시적으로 SubRectangle의 prototype객체를 Rectangle클래스의 인스턴스로 연결시킨다.
  13. SubRectangle.prototype = new Rectangle();

  14. //SubRectangle.prototype은 상속을 위하여 Rectangle()로 설정하였지만 실제 width와 height는 Rectangle에 있는 것을 쓰지 않을 것이므로 다음과 같이 프로퍼티를 제//거해준다.
  15. delete SubRectangle.prototype.width;
  16. delete SubRectangle.prototype.height;

  17. //현재 SubRectangle.prototype.constructor는 Rectangle을 참조하고 있기 때문에, 명시적으로 수정해주어야 한다.
  18. SubRectangle.prototype.constructor = SubRectangle;
생성자 체이닝(chaining)

자바스크립트의 서브클래스에서 수퍼클래스의 생성자를 명시적으로 호출해주는 것을 생성자 체이닝이라고 한다. 위에서의 방법이 다소 번거롭다면 다음과 같이 가독성을 높일 수도 있다.

  1. SubRectangle.prototype.superclass = Rectangle;
  2. function SubRectangle(x,y,w,h){
  3. this.superclass(w,h);
  4. this.x = x;
  5. this.y = y;
  6. }

이렇게 함으로서 번거롭게 call을 호출하는 것을 피할 수 있다.

call 메소드에 대해서 간단히 집고 넘어가면, 특정 객체에서 일회용 메소드를 쓰는 것이라고 생각하면 쉽다.

  1. f.call(o,1,2)
  2. //위의 코드는 다음과 같이 생각하면 된다.
  3. o.temp = f;
  4. o.temp(1,2);
  5. delete o.temp;
재정의된 메소드 호출하기

보통은 수퍼클래스에 있는 메소드를 하위클래스에서 재정의하지만, 때로는 재정의가 아닌 "확장"을 할 때가 있다. 이럴 경우 수퍼클래스의 메소드를 호출할 수 있어야 한다.

  1. Rectangle.prototype.toString = function(){
  2. return "[" + this.width + "," + this.height + "]";
  3. }
  4. SubRectangle.prototype.toString = function(){
  5. // 상위클래스에 체이닝
  6. return "[" + this.x+ "," + this.y+ "]" + Rectangle.prototype.toString.apply(this);
  7. }

만약 Rectangle.prototype.superclass 를 정의 하였다면 다음과 같이 할 수 있다. 이렇게 하는 것이 가독성에 좋을 것이다.

  1. SubRectangle.prototype.toString = function(){
  2. return "[" + this.x+ "," + this.y+ "]" + this.superclass.prototype.toString.apply(this);
  3. }

상속 없이 확장하기

자바스크립트는 함수들을 복사하거나 빌려(borrow)갈 수 있기때문에, 위에서 설명한 방법 말고도 다른 방식으로도 상속구조를 구현할 수 있다.

즉 수퍼클래스의 모든 메소드를 서브클래스의  프로토타입 객체에 복사하는 것으로 가능하다.

  1. function borrowMethods(borrowFrom, addTo){
  2. var from = borrowFrom.prototype;
  3. var to = addTo.prototype;
  4. for(m in from){
  5. if(typeof from[m] != 'function') continue; //함수가 아닌 것은 무시
  6. to[m] = from[m];
  7. }
  8. }

보통 자체적으로는 특정 작업을 하지는 않지만, 메소드들을 다른 클래스나 메소드에 포함시켜서 유용하게 사용되어질 수 있는데, 이런 목적으로 만들어진 클래스를 믹스인(mixin)클래스 혹은 믹스인이라고 한다.

  1. function genericToString(){}
  2. GenericToString.prototype.toString = function(){
  3. var props = [];
  4. for(var name in this){
  5. if(!this.hasOwnProperty(name)) continue;
  6. var value = this[name];
  7. var s = name + ':';
  8. switch( typeof value){
  9. case 'function':
  10. s += 'function';
  11. break;
  12. case 'object':
  13. if(value instanceof Array) s += 'array';
  14. else s += value.toString();
  15. break;
  16. default:
  17. s+=String(value);
  18. break;
  19. }
  20. props.push(s);
  21. }
  22. return "{" + props.join(", ") + "}";
  23. }

위의 믹스인 클래스는 다음과 같이 사용될 수 있다.

  1. function Rectangle(x,y,w,h){
  2. this.x = x;
  3. this.y = y;
  4. this.w = w;

    this.h = h;

  5. }
  6. Rectangle.prototype.area = function(){ returne this.width * this.hegith; }
  7.  
  8. borrowMethods(GenericEquals, Rectangle);

생성자를 포함한 메소드를 빌려올 수도 있다.

  1. function colored(c){ this.color = c;}
  2. colored.prototype.getColor = function(){return this.c}

    function ColoredRectangle(x,y,w,h,c){
  3. this.superclass(x,y,w,h);
  4. Colored.call(this, c);
  5. }
  6. ColoredRectangle.prototype = new Rectangle();
  7. ColoredRectangle.prototype.constructor = ColoredRectangle;
  8. ColoredRectangle.prototype.superclass = Rectangle;

  9. borrowMethods(Colored, ColoredRectangle);

객체 타입 판단하기

가장 대표적으로 typeof를 사용할 수 있다. 혼란스러울 수 있는 부분이 있는데, typeof undefined => 'undefined'이지만 typeof null => 'object'이다. 그리고 배열은 'object', 함수는 객체이긴 하지만 'function'으로 정의되어 있다.

instanceof와 constructor

어떤 값이 기본 타입이 아닌 객체라고 예측할 수 있다면 instanceof를 통해서 타입을 검사할 수 있다.

  1. x instanceof Array

만약 f가 함수라고 한다면 다음은 모두 참이다

  1. typeof f == 'function
  2. f instanceof Function
  3. f intanceof Object

만약 명확하게 어느 클래스에 속해 있는지 알고 싶다면? instanceof를 사용한다면 해당 클래스의 수퍼클래스 모두에 대해서 true를 반환한다. 이럴 경우에는 constructor 프로퍼티를 검사하여 해결할 수 잇다.

  1. var d = new Date();
  2. d instanceof Object //true
  3. d.constructor == Object //false
오티 타이핑

"어떤 클래스가 정의한 모든 메서드를 구현하면, 그것은 그 클래스의 인스터인 것으로 간주한다."로 설명될 수 있다.

학술용어로 동질이형성(allomorphism)이라고 한다. 오리 타이핑은 다른 클래스에서 메소드를 빌려오는 클래스들 연결할 때 유용하다.

이 글은 스프링노트에서 작성되었습니다.

Posted by
Prototype.js 완전정복

javascript를 통한 좀 더 편리한 Ajax application 구현을
도와주는 framework인 prototype.js에 대한 책이다.
멘토로부터 이런 책이 나왔다는 소식을 듣고나서 서점에서 한두번
힐끗 힐끗 보아 오다가 이틀 전에 질렀다.
4Gadget을 구현 할때도
멘토로부터 prototype에 대해서 몇번 들은바가 있지만 그때가지만해도
그것을 javascript의 내장프로퍼티(built-in)로 잘못 이해하고 있었다.
그때 이러한 framework를 알았더라면 DOM controll을 위해서
수십번 document.getElementByTagName('~~') 하는일도 줄였을 것이고
OO적인 코딩을 좀 더 깔끔하게 구현할 수 있었을텐데 말이다.
아쉽다...ㅡㅜ
지금이라도 알았으니 다행이다. 그래도 그때 그만큼 삽질을 해봐서 그런지
책읽듯이 읽어내려가고 있다.
Posted by