Section 9.8. Example: A defineClass( ) Utility Method


9.8. Example: A defineClass( ) Utility Method

This chapter ends with a defineClass( ) utility method that ties together the previous discussions of constructors, prototypes, subclassing, and borrowing and providing methods. Example 9-10 shows the code.

Example 9-10. A utility function for defining classes

 /**  * defineClass( ) -- a utility function for defining JavaScript classes.  *  * This function expects a single object as its only argument.  It defines  * a new JavaScript class based on the data in that object and returns the  * constructor function of the new class.  This function handles the repetitive  * tasks of defining classes: setting up the prototype object for correct  * inheritance, copying methods from other types, and so on.  *  * The object passed as an argument should have some or all of the  * following properties:  *  *      name: The name of the class being defined.  *            If specified, this value will be stored in the classname  *            property of the prototype object.  *  *    extend: The constructor of the class to be extended. If omitted,  *            the Object( ) constructor will be used. This value will  *            be stored in the superclass property of the prototype object.  *  * construct: The constructor function for the class. If omitted, a new  *            empty function will be used. This value becomes the return  *            value of the function, and is also stored in the constructor  *            property of the prototype object.  *  *   methods: An object that specifies the instance methods (and other shared  *            properties) for the class. The properties of this object are  *            copied into the prototype object of the class. If omitted,  *            an empty object is used instead. Properties named  *            "classname", "superclass", and "constructor" are reserved  *            and should not be used in this object.  *  *   statics: An object that specifies the static methods (and other static  *            properties) for the class. The properties of this object become  *            properties of the constructor function. If omitted, an empty  *            object is used instead.  *  *   borrows: A constructor function or array of constructor functions.  *            The instance methods of each of the specified classes are copied  *            into the prototype object of this new class so that the  *            new class borrows the methods of each specified class.  *            Constructors are processed in the order they are specified,  *            so the methods of a class listed at the end of the array may  *            overwrite the methods of those specified earlier. Note that  *            borrowed methods are stored in the prototype object before  *            the properties of the methods object above. Therefore,  *            methods specified in the methods object can overwrite borrowed  *            methods. If this property is not specified, no methods are  *            borrowed.  *  *  provides: A constructor function or array of constructor functions.  *            After the prototype object is fully initialized, this function  *            verifies that the prototype includes methods whose names and  *            number of arguments match the instance methods defined by each  *            of these classes. No methods are copied; this is simply an  *            assertion that this class "provides" the functionality of the  *            specified classes. If the assertion fails, this method will  *            throw an exception. If no exception is thrown, any  *            instance of the new class can also be considered (using "duck  *            typing") to be an instance of these other types.  If this  *            property is not specified, no such verification is performed.  **/ function defineClass(data) {     // Extract the fields we'll use from the argument object.     // Set up default values.     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 may be a single constructor or an array of them.     if (!data.borrows) borrows = [];     else if (data.borrows instanceof Array) borrows = data.borrows;     else borrows = [ data.borrows ];     // Ditto for the provides property.     if (!data.provides) provides = [];     else if (data.provides instanceof Array) provides = data.provides;     else provides = [ data.provides ];     // Create the object that will become the prototype for our class.     var proto = new superclass( );     // Delete any noninherited properties of this new prototype object.     for(var p in proto)         if (proto.hasOwnProperty(p)) delete proto[p];     // Borrow methods from "mixin" classes by copying to our prototype.     for(var i = 0; i < borrows.length; i++) {         var c = data.borrows[i];         borrows[i] = c;         // Copy method properties from prototype of c to our prototype         for(var p in c.prototype) {             if (typeof c.prototype[p] != "function") continue;             proto[p] = c.prototype[p];         }     }     // Copy instance methods to the prototype object     // This may overwrite methods of the mixin classes     for(var p in methods) proto[p] = methods[p];     // Set up the reserved "constructor", "superclass", and "classname"     // properties of the prototype.     proto.constructor = constructor;     proto.superclass = superclass;     // classname is set only if a name was actually specified.     if (classname) proto.classname = classname;     // Verify that our prototype provides all of the methods it is supposed to.     for(var i = 0; i < provides.length; i++) {  // for each class         var c = provides[i];         for(var p in c.prototype) {   // for each property             if (typeof c.prototype[p] != "function") continue;  // methods only             if (p == "constructor" || p == "superclass") continue;             // Check that we have a method with the same name and that             // it has the same number of declared arguments.  If so, move on             if (p in proto &&                 typeof proto[p] == "function" &&                 proto[p].length == c.prototype[p].length) continue;             // Otherwise, throw an exception             throw new Error("Class " + classname + " does not provide method "+                             c.classname + "." + p);         }     }     // Associate the prototype object with the constructor function     constructor.prototype = proto;     // Copy static properties to the constructor     for(var p in statics) constructor[p] = data.statics[p];     // Finally, return the constructor function     return constructor; } 

Example 9-11 shows sample code that uses the defineClass( ) method.

Example 9-11. Using the defineClass( ) method

 // A Comparable class with an abstract method // so that we can define classes that "provide" Comparable. var Comparable = defineClass({     name: "Comparable",     methods: { compareTo: function(that) { throw "abstract"; } } }); // A mixin class with a usefully generic equals( ) method for borrowing 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;             }             // Now make sure that this object doesn't have additional props             var propsInThis = 0;             for(name in this) propsInThis++;             // If this has additional properties, then they are not equal             if (propsInThis != propsInThat) return false;             // The two objects appear to be equal.             return true;         }     } }); // A very simple Rectangle class that provides Comparable 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 }); // A subclass of Rectangle that chains to its superclass constructor, // inherits methods from its superclass, defines an instance method and // a static method of its own, and borrows an equals( ) method. var PositionedRectangle = defineClass({     name: "PositionedRectangle",     extend: Rectangle,     construct: function(x,y,w,h) {         this.superclass(w,h);  // chain to superclass         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] }); 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net