출처: 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] }); |