IT_Programming/JavaScript

자바스크립트 완벽가이드 - 9.8 예:유틸리티 메서드인 defineClass()

JJun ™ 2015. 1. 2. 00:51

 


 출처: http://wiki.codekin.com/index.php/9.8_%EC%98%88:%EC%9C%A0%ED%8B%B8%EB%A6%AC%ED%8B%B0_%EB%A9%94%EC%84%9C%EB%93%9C%EC%9D%B8_defineClass()


 


 

 function defineClass(data) {
  
// 전달인자 객체에서 우리가 사용할 필드를 추출
  
// 기본값을 설정
   var classname
= data.name;
   var superclass
= data.extend || Object;
   var constructor
= data.construct || function() {};
   var methods
= data.methods || {};
   var statics
= data.statics || {};
   var borrows
;
   var provides
;
 
  
// borrows는 생성자 한 개이거나 생성자의 배열일 수 있다
  
if(!data.borrows) borrows = [];
  
else if(data.borrows instanceof Array) borrows = data.borrows;
  
else borrows = [ data.borrows ];
 
   provides 프로퍼티에 대해서도 똑같은 일을 수행
  
if(!data.provides) provides = [];
  
else if(data.provides instanceof Array) provides = data.provides;
  
else provides = [ data.provides ];
 
  
// 새로 만들어질 클래스의 프로토타입이 될 객체 생성
   var proto
= new superClass();
 
  
//  새로운 프로토타입 객체의 프로퍼티 중 상속받지 않은 것은 삭제
  
for(var p in proto)
     
if(proto.hasOwnProperty(p)) delete proto[p];
 
  
// 믹스인 클래스에서 프로토타입으로 메서드를 복사해서 빌려온다
  
for(var i=0; i<borrows.length; i++) {
      var c
= data.borrows[i];
      borrows
[i] = c;
     
// 메서드 프로퍼티들을 c의 프로토타입에서 우리가 만든 프로토타입으로 복사
     
for(var p in c.prototype) {
        
if(typeof c.prototype[p] != "function") continue;
         proto
[p] = c.prototype[p];
     
}
  
}
 
  
// 인스턴스 메서드를 프로토타입 객체로 복사. 이 작업은 믹스인 클래스의 메서드를 덮어쓸 수도 있음
  
for(var p in methods) proto[p] = methods[p];
 
  
// 프로토타입의 예약된 프로퍼티인 constructor와 superclass, classname을 설정
   proto.
constructor = constructor;
   proto.
superclass = superclass;
  
// classname은 이름이 실제로 기술되어 있을 때만 설정됨
  
if(classname) proto.classname = classname;
 
  
// 우리가 만든 프로토타입이 제공하기로 된 모든 메서드를 제공하는지 검증
  
for(var i=0; i<provides.length; i++) {    // 각 클래스에 대해서
      var c
= provides[i];
     
for(var p in c.prototype) {   // 각 프로퍼티에 대해
        
if(typeof c.prototype[p] != "function") continue;   // 오로지 메서드만
        
if(p == "constructor" || p == "superclass") continue;
        
// 같은 이름의 메서드가 있고, 선언된 전달인자의 수가 같은지를 검사. 그렇다면 계속 진행
        
if(p in proto && typeof proto[p] == "function" && proto[p].length == c.prototype[p].length) continue;
        
// 그렇지 않다면 예외 발생
        
throw new Error("Class" + classname + "does not provide method" + c.classname + "." + p);
     
}     
  
}
 
  
// 프로토타입 객체와 생성자 함수를 연결
   constructor.
prototype = proto;
 
  
// 정적인 프로퍼티를 생성자로 복사
  
for(var p in statics) constructor[p] = data.statics[p];
 
  
// 생성자 함수를 반환
  
return constructor;
 
}

 

 

  • definedClass() 메서드를 사용하는 예

// Comparable을 제공하는 클래스를 정의할 수 있게 추상 메서드를 가진 Comparable 클래스
var Comparable = defineClass({
   name: "Comparable",
   methods: { compareTo: function(that) {throw "abstract";} }
});
 
// 믹스인 클래스와 빌려주기 위해 만든 유용한 일반 equals() 메서드
var GenericEquals = defineClass({
   name: "GenericEquals",
   methods: {
      equals: function(that) {
         if(this == that) return true;
         var propsInThat = 0;
         for(var name in that) {
            propsInThat++;
            if(this[name] !== that[name]) return false;
         }
 
         // 이제 이 객체에 추가적인 프로퍼티가 없다는 사실을 확실히 한다
         var propsInThis = 0;
         for(name in this) propsInThis++;
 
         // 만약 추가적인 프로퍼티가 있다면 이들은 동등하지 않다 
         if(propsInThis != propsInThat) return false;
         // 두 객체는 동등해 보인다
         return true;
      }
   }
});
 
// Comparable 을 제공하는 아주 간단한 Rectangle 클래스
var Rectangle = defineClass({
   name: "Rectangle",
   construct: function(w,h) { this.width = w; this.height=h; },
   methods: {
      area: function() { return this.width * this.height; },
      compareTo: function(that) { return this.area() - that.area(); }
   },
   provides: Comparable
});
 
// Rectangle 의 서브클래스이고 상위 클래스의 생성자에 체이닝, 상위 클래스에서 메서드를 상속받음
// 자신만의 인스턴스 메서드와 정적인 메서드를 정의하며 equals()메서드를 빌려온다
var PositionedRectangle = defineClass({
   name: "PositionedRectangle",
   extend: Rectangle,
   construct: function(x,y,w,h) {
      this.superclass(w,h);   // 상위 클래스에게 체이닝
      this.x = x;
      this.y = y;
   },
   methods: {
      isInside: function(x,y) {
         return x > this.x && x < this.x + this.width && y > this.y && y < this.y + this.height;
      }
   },
   statics: {
      comparator: function(a,b) { return a.compareTo(b); }
   },
   borrows: [GenericEquals]
 });