关于JavaScript中“类”的思考

为什么要有类?

    众所周知,JavaScript是没有像Java那样的类。Java的对象需要通过类来实例化才能创建,而JavaScript是没有这个必要的,那么JavaScript需要类来做什么呢?一个Java类可以实例化N个一模一样的对象,如果从这个角度来看,JavaScript中,一个可以构造N个一样对象的函数就可以称作是“类”了呢?

JavaScript中类的实现

我本人实战经验不多,只看过基本书,知道的方法有以下几种。

1、两个方向

伪类

JavaScript提供了一个伪类的机制。

var Duck = function (){
    this.name = 'duck';
};

    通过调用var aDuck = new Duck();即可获得一个拥有name属性的对象。在这个过程中,new函数是先创建了一个空的对象,用this关键字引向了这个对象,然后this.name=’duck’;执行的时候相当于为这个空对象添加了name属性,最后函数自动返回了此对象。这个过程看起来很完美,但是万一使用过程中我们完了new这个关键字呢?这时的this指向的是window全局对象,而这个函数只会为window添加了一个name的全局变量后,返回一个undefined。这样的失误是很麻烦的,污染了全局变量就像埋下了定时炸弹。

    所以老道在《JavaScript语言精粹》中提出说,干脆不用new了,也就是函数化

函数化
var duck = function(){
    var that = {};
    that.name = ‘duck’;
    return that;
};

    这样var aDuck = duck();就可以做出一个对象了,而且全局变量很安全。

    这两个方法看起来差别不是很大,只是一个new,一个不new而已。其实有很大的区别的。这两个例子我们都没有考虑类里面定义的方法。假如我们要添加一个getName的方法,应该怎么做呢?

伪类方法

var Duck = function (){
    this.name = 'duck';
};
Duck.prototype.getName = function(){
    alert(this.name);
};

函数化方法

var duck = function(){
    var that = {};
    that.name = 'duck';
    that.getName = function(){alert(this.name);}
    return that;
};

    在伪类方法中,这里的getName方法在内存只占用了一份,因为对象的原型都是同一个对象,getName只是一个方法。而函数化方法中,实例化了多少个对象,内存就有多少份getName的拷贝。

2、关于共享函数的方法

    做前端的都是完美主义者,“只占一份内存”和“N份内存”之间肯定选择的是前者,即使冒着污染全局变量的危险。但我不甘心,我想让函数化方法也可以共享一个方法。我做了一些尝试。

说起公用,我想当的是原型和闭包。

    我能不能让函数方法实例化的每个对象都指向一个原型,那么为该原型添加的方法就可以公用了。

原型失败的例子

var duck = function(){
    if(! Arguments.callee.myproto){
    //如果没有原型,这定义一个
    var proto = arguments.callee.myproto = function(){};
    //为原型添加方法
    proto.getName = function(){alert(this.name);};
   }
    //让所有的对象接上原型
    var that = new aruguments.callee.myproto();
    that.name = 'duck';
    return that;
};

    这个方法一写出来我就有点后悔了。不仅仅是因为很复杂,还因为使用了new。并没让函数化的好处体现出来。

另外一个尝试使用闭包

var duck = function(){
    //这个函数藏在闭包内
    var getName = function(){alert(this.name);};
    return function(){
        var that = {};
        that.name = 'duck';
        that.getName = getName;
        return that;
    };
}();//这对括号不是多余的。

    懂闭包的人应该很轻松看懂这行代码。getName藏在闭包内只定义了一次,每个对象调用getName的时候都会回到闭包内找到这个函数。

    这段代码看起来看起来不错。但是我们还有问题需要考虑。

3、Private属性

    无论刚才说的哪种方法,我们都默认属性是public的,假如我们要为duck添加age这个private的属性,这个属性只能通过某个特定的方法访问,且不能被修改。

    说到这种问题,脑海里面唯一的答案就是“闭包”。

先给一个错误的例子:

var duck = function(){

    //这个函数藏在闭包内
    var getName = function(){alert(this.name);};
    //这样写是错的
    var getAge = function(){
        if(this.age > 30) alert('不告诉你');
        else alert(this.age);
    };
    return function(duckAge){
        var that = {};
        var age = duckAge;
        //不能直接that.age = duckAge!
        that.name = 'duck';
        that.getName = getName;
        return that;
    };

}();//这对括号不是多余的。

    这样的代码是错的,因为getAge函数中,this指向的是该对象,age属性不是直接加在该对象上的,否则谁都可以this.age访问修改了吧。那么我们在getAge函数中把this去掉呢,这样也是很显然不对的。

    我们怎么办?不能怎么办了。这能老老实实地让getName占多份内存了。

    又回到了“占n份内存了”,我已经没有办法了。只能说为了private只能牺牲性能了。但需要private属性的机会其实也不多。

    另外,采用伪类法添加private变量也没有特别的高招。

var Duck = function (age){
    this.name = 'duck';
    this.getAge = function(){
        if(age > 30) alert('不告诉你');
        else alert(age);
    };
};
Duck.prototype.getName = function(){
    alert(this.name);
};

接下来是更重要的问题:继承

4、继承

    我们需要代码重用,而重用的一般方法是继承组合。这里先不讲组合。

考虑下,我们需要一个ColorDuck类,它比duck类多一个color的属性。其实主要采用的方法称为差异化继承”。主要思想为复制一份父类的实例(即对象),然后哪里不一样就修改哪里。

函数化的实现

父类:

var duck = function(){
//这个函数藏在闭包内
    var getName = function(){alert(this.name);};
    return function(duckAge){
        var that = {};
        that.name = 'duck';
        that.getName = getName;
        var age = duckAge;
        that.getAge = function(){
            if(age > 30) alert('不告诉你');
            else alert(age);
        };
        return that;
    };
}();//这对括号不是多余的。

子类:

var ColorDuck = function(age,color){
    var that = duck(age);
    that.color = color;
    return that;
};//完了。

var that = duck(age);这行语句让子类对象拥有了父类对象的所有属性和方法,然后根据差异添加属性方法即可。

对于伪类的实现就稍微要多考虑一些东西。

先看一段错误代码。

父类:

var Duck = function (age){
    
    this.name = 'duck';
    this.getAge = function(){
        if(age > 30) alert('不告诉你');
        else alert(age);
    };
};

Duck.prototype.getName = function(){
    alert(this.name);
};

子类:

var ColorDuck = function(age,color){
    //要调用父类的方法实例化对象
    //var that = new duck();这样写是错的,因为此this不是那个this.
    Duck.call(this,age);//这个this才是那个this。
    this.color = color;
};
ColorDuck.prototype = Duck.prototype;

    这样算是完成了吗?不是的。

    问题出在这行。ColorDuck.prototype = Duck.prototype;子类的prototype不应该和父类的prototype为同一个对象,否则我们为子类添加函数的时候,父类也会增加。而这个情况不是我们希望出现的。所以我们要为子类添加一个自己的原型,而同时这个原型又必须有父类原型的方法。那不就是父类的实例嘛。

ColorDuck.prototype = new Duck();

ColorDuck.prototype.constructor = ColorDuck;

    这样继承算是OK了。完整代码如下:

父类:

var Duck = function (age){
    
    this.name = 'duck';
    this.getAge = function(){
        if(age > 30) alert('不告诉你');
        else alert(age);
    };
};

Duck.prototype.getName = function(){
    alert(this.name);
};

子类:

var ColorDuck = function(age,color){
    
    //要调用父类的方法实例化对象
    //var that = new duck();这样写是错的,因为此this不是那个this.
    Duck.call(this,age);//这个this才是那个this。
    this.color = color;
}; 
ColorDuck.prototype = new Duck();
ColorDuck.prototype.constructor = ColorDuck;

总结:

    我只是菜鸟,如果上述说得不对,希望指正!此贴不是教学贴,这是把我的思路总结一下而已,谢谢各位耐心看完。

    JavaScript里头没有最好的实现,只有最适合的实现。这就是JavaScript的魅力。

posted @ 2011-05-30 03:13  xiiiiiin  阅读(265)  评论(0编辑  收藏  举报