`
robinqu
  • 浏览: 88673 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

JavaScript Class模拟深入 - Duck Typing 和 Class相关的工具类

阅读更多
Duck Typing
引用
If it implements all the methods defined by a class, it is an instance of that class.

Duck typing is particularly useful in conjunction with classes that "borrow" methods from other classes. Earlier in the chapter, a Rectangle class borrowed the implementation of an equals( ) method from another class named GenericEquals. Thus, you can consider any Rectangle instance to also be an instance of GenericEquals. The instanceof operator will not report this, but you can define a method that will.


Duck Typing的假设如下,如果一个对象实现了一个类中的所有方法,那么这个对象就是这个类的实例。

Duck Typing和之前讲的“方法借用”一起就非常有用,在Duck Typing的假设下,你的确可以说之前例子中的Rectangle是GenricEquals的实例。但是instanceof并不这么认为,我们只有自己写一个方法来判断:

// Return true if each of the method properties in c.prototype have been
// borrowed by o. If o is a function rather than an object, we
// test the prototype of o rather than o itself.
// Note that this function requires methods to be copied, not
// reimplemented.  If a class borrows a method and then overrides it,
// this method will return false.
function borrows(o, c) {
    // If we are an instance of something, then of course we have its methods
    if (o instanceof c) return true;

    // It is impossible to test whether the methods of a built-in type have
    // been borrowed, since the methods of built-in types are not enumerable.
    // We return undefined in this case as a kind of "I don't know" answer
    // instead of throwing an exception. Undefined behaves much like false,
    // but can be distinguished from false if the caller needs to.
    if (c == Array || c == Boolean || c == Date || c == Error ||

        c == Function || c == Number || c == RegExp || c == String)
        return undefined;

    if (typeof o == "function") o = o.prototype;
    var proto = c.prototype;
    for(var p in proto) {
        // Ignore properties that are not functions
        if (typeof proto[p] != "function") continue;
        if (o[p] != proto[p]) return false;
    }
    return true;
}


引用
The borrows( ) method of Example 9-7 is relatively strict: it requires the object o to have exact copies of the methods defined by the class c.

TRue duck typing is more flexible: o should be considered an instance of c as long as it provides methods that look like methods of c.



这个borrow()函数严格的判断这个对象是否实现了类中的所有方法,但有时候我们的Duck Typing需要更加灵活。我们只需要这个对象提供了同名方法,以及方法的参数一致就可以了。
下面是实现这种判定的函数:

// Return true if o has methods with the same name and arity as all
// methods in c.prototype. Otherwise, return false.  Throws an exception
// if c is a built-in type with nonenumerable methods.
function provides(o, c) {
    // If o actually is an instance of c, it obviously looks like c
    if (o instanceof c) return true;

    // If a constructor was passed instead of an object, use its prototype
    if (typeof o == "function") o = o.prototype;

    // The methods of built-in types are not enumerable, and we return
    // undefined.  Otherwise, any object would appear to provide any of
    // the built-in types.
    if (c == Array || c == Boolean || c == Date || c == Error ||
        c == Function || c == Number || c == RegExp || c == String)
        return undefined;

    var proto = c.prototype;
    for(var p in proto) {  // Loop through all properties in c.prototype
        // Ignore properties that are not functions
        if (typeof proto[p] != "function") continue;
        // If o does not have a property by the same name, return false
        if (!(p in o)) return false;
        // If that property is not a function, return false
        if (typeof o[p] != "function") return false;
        // If the two functions are not declared with the same number
        // of arguments, return false.
        if (o[p].length != proto[p].length) return false;
    }
    // If all the methods check out, we can finally return true.
    return true;
}


下面给出一个实际的使用环境。假设Comparable()是一个通用的API接口,我们用provide()函数就可以判定某个对象是否拥有了Comparable中的同名方法,也就是有没有类似的compareTo()方法:

function Comparable( ) {}
Comparable.prototype.compareTo = function(that) {
    throw "Comparable.compareTo( ) is abstract.  Don't invoke it!";
}

// Check whether objects o and p can be compared
// They must be of the same type, and that type must be comparable
if (o.constructor == p.constructor && provides(o, Comparable)) {
    var order = o.compareTo(p);
}


最后给一个定义类的函数,老外实在太NB了,自己看注释吧,虽然很长不过很清晰:

/**
 * 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;
}


演示一下这东西怎么用:
// 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]
});
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics