实例说明如何在JavaScript中定义子类

作者:admin     字体:[增加 减小]    类型:原创
JavaScript的对象可以从类的原型对象中继承属性(通常继承的是方法)。如果O是类B的实例,B是A的子类,那么O也一定从A中继承了属性。为此,要确保B的原型对象继承自A的原型对象。

JavaScript的对象可以从类的原型对象中继承属性(通常继承的是方法)。如果O是类B的实例,B是A的子类,那么O也一定从A中继承了属性。为此,要确保B的原型对象继承自A的原型对象。通过inherit()函数,可以这样实现:

//通过原型继承创建一个新对象
//inherit() 返回一个继承自原型对象p的属性的新对象
//这里使用ECMASscript 5中的object.create()函数(如果存在的话)
//如果不存在,则使用其他方法
function inherit(p) {
    if ( p==null ) throw TypeError();   //p是一个对象,但不能是null
    if ( Object.create )        //如果Object.create()存在
        return Object.create(p);    //直接使用它
    var t = typeof p;       //否则进一步检测
    if ( t!=='object' && t!=='function' ) throw TypeError();
    function f(){};         //定义一个空构造函数
    f.prototype = p;        //将其原型属性设置为p
    return new f();         //使用f()创建p的继承对象
}
B.prototype = inherit(A.prototype); //子类派生父类
B.protoype.constructor = B; //重载继承来的constructor属性

上面这两行代码是在JavaScript中创建子类的关键。如果不这样做,原型对象仅仅是一个普通对象,它只继承Object.prototype,这意味着你的类和所有的类是一样是Object的子类。如果将这两行代码添加至defineClass()函数中,可以将它变成如下:


//用一个简单的函数创建简单的子类 function defineSubclass ( superclass, //父类的构造函数 constructor, //新的子类的构造函数 methods, //实例方法:复制至原型中 statics) //类属性:复制至构造函数中 { //建立子类的原型对象 constructor.prototype = inherit(superclass.prototype); constructor.prototype.constructor = constructor; //像对常规类一样复制方法和类属性 if ( methods ) extend(constructor.prototype,methods); if (statics) extend(constructor,statics); //返回这个类 return constructor; } //以构造函数为父类,在其基础上扩展子类 Function.prototype.extend = function(constructor,methods,statics){ return defineSubclass(this,constructor,methods,statics); }

我们写一个集合类,如下:

//集合类
//集合(set)是一种数据结构,用以表示非重复值的无序集合
function Set( ){
    this.values = {};
    this.n = 0;
    this.add.apply(this,arguments);
}

//将每个参数都添加至集合中
Set.prototype.add = function() {
    for ( var i=0;i<arguments.length;i++ ) {    //遍历每个参数
        var val = arguments[i];     //待添加到集合的值
        var str = Set._v2s(val);    //把它转换为字符串
        if ( !this.values.hasOwnProperty(str) ) {   //如果不在集合中
            this.values[str] = val; //将字符串和值对应起来
            this.n++;   //集合中值的计数加一
        }
    }
    return this;    //支持链式方法调用
}

//从集合删除元素,这些元素由参数指定
Set.prototype.remove = function() {
    var self = this;
    Array.prototype.map.call(arguments,function(e){ //遍历每个参数
        var str = Set._v2s(e);  //将字符串和值对应起来    
        if( self.values.hasOwnProperty(str) ) { //如果它在集合中
            delete self.values[str];        //删除它
            this.n--;   //集合中值的计数减一
        }
    });
    return this;    //支持链式方法调用
}


//如果集合包含这个值,则返回true,否则,返回false
Set.prototype.contains = function(value) {
    return this.values.hasOwnProperty(Set._v2s(value));
}

//返回集合的大小
Set.prototype.size = function() {
    return this.n;
}

//遍历集合中的所有幸免于难,在指定的上下文中调用f
Set.prototype.foreach = function(f,context) {
    for( var s in this.values ) {   //遍历集合中的所有键值
        if (this.values.hasOwnProperty(s))  //忽略继承的属性
            f.call(context,this.values[s]); //调用f,传入value
    }
}

//这是一个内部函数,用以将任意JavaScript值和唯一的字符串对应起来
Set._v2s = function( val ) {
    var self = this;
    switch( val ) {
        case undefined : return 'u';    //特殊的原始值
        case null:  return 'n';         //值只有一个字母
        case true:  return 't';
        case false: return 'f';
        default: switch( typeof val ) {
            case 'number': return '#' + val;    //数字都带有#前缀
            case 'string': return '"' + val;    //字符串都带有"前缀
            default: return '@' + objectId(val);    //Objs and funcs get @
        }   
    }

    function objectId(o) {
        var prop = "|**objectid**|";    //私有属性,用以存放id
        if (!o.hasOwnProperty(prop) ) { //如果对象没有id
            o[prop] = self._v2s.next++; //将下一个值赋给它
        }
        return o[prop];     //返回这个id
    }
}

Set._v2s.next = 1000;   //设置初始id的值

extend(Set.prototype,{
    toString: function() {
        var s = "{",
            i = 0;
        this.foreach(function(v){
            s += ((i++>0) ? ", " : "") + v;
        });
        return s + "}";
    },
    toLocaleString: function() {
        var s = "{",i=0;
        var args = arguments;
        this.foreach(function(v){
            if (i++>0) s+= ", ";
            if (v==null) s+=v;
            else s +=v.toLocaleString();
        });
        return s+"}";
    },
    toArray: function() {
        var a = [];
        this.foreach(function(v){
            a.push(v);
        });
        return a;
    },
    equals: function(that){
        //一些次要情况的快捷处理
        if( this===that ) return true;

        //如果that对象不是一个集合,它和this不相等
        //我们用到了instanceof,使得这个方法可以用于Set的任何子类
        //如果希望采用鸭式辩证的方法,可以降低检查的严格程度
        //或者可以通过 this.constructor==that.constructor 来加检查的严格程度
        //注意,null和undefined两个值是无法用于instanceof运算符
        if ( !(that instanceof Set) ) return false;

        //如果两个集合的大小不一样,则它们不相等
        if ( this.size()!=that.size() ) return false;

        //现在检查两个集合中的元素是否完全一样
        //如果两个集合不相等,则通过抛出异常来终止foreach循环
        try{
            this.foreach(function(v) { if (!that.contains(v)) throw false;});
            return true;    //所有的元素都匹配,两个集合相等
        } catch (x) {
            if ( false===x ) return false;  //如果集合中有元素在另外一个集合中不存在
            throw x;    //重新抛出异常
        }       
    }

});

//定义一个扩展函数,用来将第二个以及后续参数复制至第一个参数
//这里我们处理了IE bug:在多数IE版本中
//如果o的属性拥有一个不可枚举的同名属性,则for/in循环
//不会枚举对象o的可枚举属性,也就是说,将不会正确处理诸如toString的属性
//除非我们显式检测它
var extend = (function(){
    //在修复它之前,首先检查是否存在bug
    for ( var p in {toString:null}) {
        //如果代码执行到这里,那么for/in循环会正确工作并返回
        return function extend(o){
            o = o || new Object;
            for ( var i=1;i<arguments.length;i++ ){
                var source = arguments[i];
                for (var prop in source) o[prop] = source[prop];
            }
            return o;
        };  
    }
    //如果代码执行到这里,说明for/in循环不会枚举测试对象的toString属性
    //因此返回另一个版本的extend()函数,这个函数显式测试
    //Object.prototype中的不可枚举属性
    return function patched_extend( o ) {
        o = o || new Object;
        for( var i=1; i<arguments.length; i++ ){
            var source = arguments[i];
            //复制所有的可枚举属性
            for (var prop in source) o[prop] = source[prop];
            //现在检查特殊属性
            for ( var j=0;j<protoprops.length;j++ ){
                prop = protoprops[j];
                if ( source.hasOwnProperty(prop) ) o[prop] = source[prop];
            }
        }
        return o;
    };
    //这个列表列出了需要检查的特殊属性
    var protoprops = ["toString","valueOf","constructor","hasOwnProperty",
        "isPrototypeOf","propertyIsEnumerable","toLocaleString"];
}());

下面定义Set的子类SingletonSet,它是一个特殊的集合,它是只读的,而且含有单独的常量成员。