Simulating class in JavaScript -- 6

9.5 Superclasses and  Subclasses

Java, C++ 和别的一些基于类的面向对象语言有着明确的类层次的概念。每个类有一个超类从它那继承方法和属性。任何类可以被扩展,或者成为子类,为的结果就是子类可以继承它的行为。正如之前展示的,JavaScript支持prototype继承取代了基于类的继承。然而,JavaScript类比类的层次是可以描画出来的。在JavaScipt里,对象类是最一般的,所有的别的类都是类对象的特别的版本,或者子类。别的方式去说明就是Object是所有的built-in类的超类,所有的类都从Object继承了一些基本的方法。

回想起对象是从它们的构造函数的prototype对象继承属性。它们是怎么从Object类继承属性的呢?记住prototype对象它本身也是一个对象。它是被Object构造函数创建的。这意味着prototype对象本身也从Object.prototype里继承了属性。基于prototype的继承并不局限在单单的一个prototype对象,反而,包括了prototype对象的链。因此,一个Complex对象从Complex.prototype和Object.prototype里继承类属性。当你查找Complex对象里的属性,首先查找的就是这个对象本身。如果这个属性没有被找到,Complex.prototype对象接下来就会被查找。最后,在这个对象里也没有找到,那么Object.prototype对象就会被搜索。

要注意的是因为Complex prototype对象是在Object prototype对象之前被搜索,Complex.prototype的属性就会隐藏了任何与Object.prototype里有着相同名字的属性。例如,Complex类这个例子,一个在Complex.prototype对象里定义的toString()方法,在Object.prototype里也定义了一个相同名字的,但是Complex对象永远都看不到这个方法,因为Complex.prototype里的toString()方法会首先被找到。

这章目前为止介绍的所有类都是Object类的直接的子类。当有需要的时候,然而,有可能作为任何别的类的子类。回想起之前说到的Rectangle类,例如,它有表示width和height的属性,但是没有表示坐标的属性。要做这个,简单确认下新的类的prototype对象它本身就是一个Rectangle的实例,这样它就能继承所有Rectangle.prototype的属性。

//Here is a simple Rectangle class.
//It has a width and height and can compute its own area
function Rectangle(w, h) {
    this.width = w;
    this.height = h;
}

Rectangle.prototype.area = function () { return this.width * this.height; }

//Here is how we might subclass it
function PositionedRectangle(x, y, w, h) {
    //First,invoke the superclass constructor on the new object
    //so that it can initialize the width and the height.
    //We use the call method so that we invoke the constructor as a 
    //method of the object to be initialized.
    Rectangle.call(this, w, h);

    //Now store the position of the upper-left corner of the rectangle
    this.x = x;
    this.y = y;
}

//If we use the default protoype object  that is created when we
//define the PositionedRectangle() construtor, we get a subclass of object.
//To subclass Rectangle, we must explicitly create our prototype object.
PositionedRectangle.prototype = new Rectangle();

//We create this prototype object for inheritance purposes,but we 
//don't actually want to inherit the width and height properties that
//each Rectangle object has, so delete them from the prototype.
delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;

//Since the prototype object was created with the Rectangle() constructor,
//it has a construtor property that refers to that constructor. But 
//we want PositionedRectangle objects to have a different constructor
//property,so we've got to reassign this default constructor property.
PositionedRectangle.prototype.constructor = PositionedRectangle;

//Now that we've configured the prototype object of our subclass,
//we can add instance methods to it.
PositionedRectangle.prototype.contains = function (x, y) { 
    return (x > this.x && x < this.x + this.width && y > this.y && y < this.y + this.height);
}

看了这个例子,就会发现创建一个子类在JavaScript里并不像创建一个直接继承Object类那样简单。首先,有个一个问题就是关于从子类的构造函数调用超类的构造函数。当你这样做的时候要小心,超类的构造函数会被调用成作为新创建的类的方法。接下来,设置子类的构造函数的prototype对象是有技巧的。你必须明确地创建这个prototype对象作为超类的实例,然后明确的设置该prototype对象的constructor属性。可选择的是,你也可以去delete任何属性是超类在prototype对象里创建的。因为重要的是prototype对象从它的prototype里继承的属性。

在已经定义好的PositionedRectangle类里,你可以把代码写成这样:


var r = new PositionedRectangle(2, 2, 2, 2);
print(r.contains(3, 3)); //invoke an instance method
print(r.area()); //invoke an inherited instance method

//Use the instance fields of the class:
print(r.x + "," + r.y + "," + r.width + "," + r.height);

//Our object is an instance of all 3 of these classes
print(r instanceof PositionedRectangle && r instanceof Rectangle && r instanceof Object);


9.5.1 Constructor Chaining

之前的代码里展示过,PositionedRectangle()构造函数需要明确地调用超类的构造函数。这种就叫构造函数链( Constructor Chaining)。当创建子类时,这就是十分普遍的了。如果你仅仅有一层的子类,你可以简化构造函数链的语法,通过添加一个子类的prototype对象的属性,叫superclass;

//Store a reference to our superclass constructor.
PositionedRectangle.prototype.superclass = Rectangle;

对于定义属性来说,更简单的语法就是:

function PositionedRectangle(x,y,w,h){
       this.superclass(w,h);
       this.x = x;
       this.y = y;
 
}
要注意的是超类的构造函数是要通过this对象来调用的。这意味着你不需要使用call或者apply方法来调用超类的构造函数作为对象的方法了。这点和JAVA很相似吧。


9.5.2 Invoking Overridden Methods

当一个子类定义一个方法与在超类中的方法有着相同的名字,那么子类的方法就会覆盖那个方法。这个一个比较普遍的做法当创建一些已经存在的类的子类。任何时候你定义一个toString()方法给类,你就重载了Object的toString()方法。

一个方法重载了另外一个常常是要增强被重载的函数的功能而不仅仅就是换掉它。要那样做,一个方法必须是能够调用这个它可以重载的方法。某种意义上,这是一种方法链,就像刚说到的构造函数那样。调用一个重载的方法比起调用超类的构造函数更加的棘手的。

让我们思考下一个例子。假设Rectangle类有一个定义好的toString()方法。

Rectangle.prototype.toString = function() {
return "[" + this.width +"," + this.height  + "]";
}

如果你给Rectangle一个toString()方法,你真的必须重载在那个在PositionedRectangle里的方法为的就是子类的实例就有一个字符串的显示可以反映他们的所有属性,而不仅仅是width和height属性。PositionedRectangle是一个足够简单的类,它的toString()方法可以仅是返回所有属性的值。但是为了达到目的,让我们来出来position类的属性和委托给它的超类,为了width和height属性。


PositionedRectangle.prototype.toString = function () {
    return "(" + this.x + "," + this.y + ")" +    //our fields
           Rectangle.prototype.toString.apply(this); // chain to superclass
}

超类的toString()方法的实现是超类的prototype对象的属性。要注意的哦是你不能直接调用它。用apply调用它为的是你可以识别哪个对象是应该被呼叫的。

如果你添加一个superclass属性给PositionedRectangle.prototype,代码可以重写成一个依赖超类方式:

PositionedRectangle.prototype.toString = function () {
    return "(" + this.x + "," + this.y + ")" +    this.superclass.prototype.toString.apply(this);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值