JavaScript Class模拟深入 - 继承、子类

Superclasses and Subclasses

之前的一篇文章讲解的类都是直接继承自Object的,这里会介绍如何在JS中实现继承自任意“类”

[quote]Java, C++, and other class-based object-oriented languages have an explicit concept of the class hierarchy.

In JavaScript, the Object class is the most generic, and all other classes are specialized versions, or subclasses, of it. Another way to say this is that Object is the superclass of all the built-in classes, and all classes inherit a few basic methods from Object.

Remember that the prototype object is itself an object; it is created with the Object( ) constructor. This means the prototype object itself inherits properties from Object.prototype!

a chain of prototype objects is involved. [/quote]

传统的OOP语言都有显示的继承,JavaScript没有这个特性。但是,JavaScript中所有对象实际上都是继承自Object的。换句话说,Object是所有其他类的“超类”。事实上,在JS中,的确其他对象都从Object中继承了一些方法和属性。

但是一个对象不是应该继承自它的Prototype对象么?答案是,Prototype对象本身也是对象啊!你不得不佩服JS的奇妙。

Prototype对象会从Object那里继承那些属性和方法。在复杂的继承树中,一个Prototype的继承链就出现了。

[quote]Suppose you want to create a subclass of Rectangle in order to add fields and methods related to the position of the rectangle. To do this, simply make sure that the prototype object of the new class is itself an instance of Rectangle so that it inherits all the properties of Rectangle.prototype.[/quote]

下面的例子中,演示了继承自Rectangle的一个例子。要这么做,你需要把新“类”的Prototype属性设置为一个Rectangle的实例。这样新的“类”就继承了所有Rectangle的属性和方法。
英文注释相当详细,我也会给出中文注释。

// Here is a simple Rectangle class.
// It has a width and height and can compute its own area
// 先定义一个原始的Rectangle
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 height.
// We use the call method so that we invoke the constructor as a
// method of the object to be initialized.
// This is called constructor chaining.
// 这里要调用“父类”的构造方法来初始化原有属性,这里一定要用call()
// 因为要特别指明是在这个子类的对象上调用父类的构造方法,否则是没有意义的
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 prototype object that is created when we
// define the PositionedRectangle( ) constructor, we get a subclass of Object.
// To subclass Rectangle, we must explicitly create our prototype object.
// 修改子类的Prototype属性的值,即为一个Rectangle实例,这样才是继承自Rectangle
// 而不是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.
// 删掉Prototype对象中的Rectangle实例里面的width和height,这些都是是没用的
// 我们在子类的构造方法中已经处理了这些属性,不需要继承,而且由于上一步
// 执行Rectangle()时没有传递参数,这个对象的实例中的属性都是undefined
delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;

// Since the prototype object was created with the Rectangle( ) constructor,
// it has a constructor 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.
// 改完了Prototype还不行,还要再把Prototype对象的Constructor属性改为指向子类的
// 构造函数。需要这一步是因为,prototype的值在是Rectangle对象,它的constructor还是
// Rectangle,我们在new的时候会出问题
PositionedRectangle.prototype.constructor = PositionedRectangle;

// Now that we've configured the prototype object for 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);
}


[quote]First, there is the issue of invoking the superclass constructor from the subclass constructor. Take care when you do this that the superclass constructor is invoked as a method of the newly created object. Next, there are the tricks required to set the prototype object of the subclass constructor. You must explicitly create this prototype object as an instance of the superclass, then explicitly set the constructor property of the prototype object.[*] Optionally, you may also want to delete any properties that the superclass constructor created in the prototype object because what's important are the properties that the prototype object inherits from its prototype.[/quote]

大致的步骤如下:
1、调用父类的构造函数;确保实在当前对象上调用
2、改变Prototype对象的值,以及Prototype对象的Constructor属性值;这些都需要你显示地给Prototype属性和Prototype.Constructor赋值,这样调用才能在new的时候调用的是子类的构造方法
3、删掉一些父类构造方法在Prototype对象中创建的、不需要再继承的属性

使用的示例:

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);



Constructor Chaining
构造函数的链接

In the example just shown, the PositionedRectangle( ) constructor function needed to explicitly invoke the superclass constructor function. This is called constructor chaining and is quite common when creating subclasses. You can simplify the syntax for constructor chaining by adding a property named superclass to the prototype object of the subclass


上面的例子里面,子类中显示的调用了父类的构造函数,这就是“Constructor Chaining”,我们可以创建一个新的标记来简化这个过程:

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


[quote]Note that the superclass constructor function is explicitly invoked through the this object. This means that you no longer need to use call( ) or apply( ) to invoke the superclass constructor as a method of that object.[/quote]

这样的好处是不需要调用call()或是apply()方法,而是通过this.superclass()调用

Invoking Overridden Methods
调用被“重载”的方法
[quote]
When a subclass defines a method that has the same name as a method in the superclass, the subclass overrides that method. This is a relatively common thing to do when creating subclasses of existing classes. [/quote]

当定义子类时,实例方法如果和父类中的方法同名,那么你就自然“重载”它了。
例如,下面这个这个示例中,Rectangle和它的子类PositionedRectangle都有toString():

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


If you give Rectangle a toString( ) method, you really must override that method in PositionedRectangle so that instances of the subclass have a string representation that reflects all their properties, not just their width and height properties. PositionedRectangle is a simple enough class that its toString( ) method can just return the values of all properties. 


PositionedRectangle中的toString()则可以通过调用父类Rectangle的toString()完成部分工作:

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


[quote]The superclass's implementation of toString( ) is a property of the superclass's prototype object. Note that you can't invoke it directly. Invoke it with apply( ) so that you can specify the object on which it should be called.[/quote]

父类的toString()并没有真正意义上被消除,依旧存在在父类的Prototype中,只是子类的的Prototype中的内容会被优先找到。
你不能直接调用父类的方法,在调用时,你必须确保实在当前对象上调用。你可以使用apply()和call()方法来帮忙。

如果你像之前那样定义了superclass属性,你也可以这样:

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


其实保证superclass在创建实例之前定义就可以了,构造函数中用this.superclass()的时候superclass还没有创建也没有关系。其实这个想想只有在new的时候才调用构造函数这一点就明白。

Extending Without Inheriting
不继承的扩展

[quote]Since JavaScript functions are data values, you can simply copy (or "borrow") the functions from one class for use in another. [/quote]

由于JS的函数本身是一种数据,你可以轻易的拷贝到你想要的地方,例如这个函数:

// Borrow methods from one class for use by another.
// The arguments should be the constructor functions for the classes.
// Methods of built-in types such as Object, Array, Date, and RegExp are
// not enumerable and cannot be borrowed with this method.
function borrowMethods(borrowFrom, addTo) {
var from = borrowFrom.prototype; // prototype object to borrow from
var to = addTo.prototype; // prototype object to extend

for(m in from) { // Loop through all properties of the prototye
if (typeof from[m] != "function") continue; // ignore nonfunctions
to[m] = from[m]; // borrow the method
}
}


这个例子中的函数帮你把一个对象中的方法“借用“到另一个对象中。

[quote]It is possible to write some methods generically so that they are suitable for use by any class, or by any class that defines certain properties.
Classes like these that are designed for borrowing are called mixin classes or mixins.[/quote]

那么你可以歇一歇通用的方法,定义其他类的时候,你可以简单的“借用”它们,这些被借用的类叫做mixin class或者mixins。下面这个例子中,我们创建了两个通用的类,一个包含通用的toString(),还有一个包含比较对象的通用equal(),并演示了如何使用借用方法:

// This class isn't good for much on its own. But it does define a
// generic toString( ) method that may be of interest to other classes.
function GenericToString( ) {}
GenericToString.prototype.toString = function( ) {
var props = [];
for(var name in this) {
if (!this.hasOwnProperty(name)) continue;
var value = this[name];
var s = name + ":"
switch(typeof value) {
case 'function':
s += "function";
break;
case 'object':
if (value instanceof Array) s += "array"
else s += value.toString( );
break;
default:
s += String(value);
break;
}
props.push(s);
}
return "{" + props.join(", ") + "}";
}

// This mixin class defines an equals( ) method that can compare
// simple objects for equality.
function GenericEquals( ) {}
GenericEquals.prototype.equals = function(that) {
if (this == that) return true;

// this and that are equal only if this has all the properties of
// that and doesn't have any additional properties
// Note that we don't do deep comparison. Property values
// must be === to each other. So properties that refer to objects
// must refer to the same object, not objects that are equals( )
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;
}


// Here is a simple Rectangle class.
function Rectangle(x, y, w, h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
}
Rectangle.prototype.area = function( ) { return this.width * this.height; }

// Borrow some more methods for it
borrowMethods(GenericEquals, Rectangle);
borrowMethods(GenericToString, Rectangle);


Borrow Constructor
你甚至可以借用构造函数。这样你就像在用“多继承”一样……

// This mixin has a method that depends on its constructor. Both the
// constructor and the method must be borrowed.
function Colored(c) { this.color = c; }
Colored.prototype.getColor = function( ) { return this.color; }

// Define the constructor for a new class.
function ColoredRectangle(x, y, w, h, c) {
this.superclass(x, y, w, h); // Invoke superclass constructor
Colored.call(this, c); // and borrow the Colored constructor
}

// Set up the prototype object to inherit methods from Rectangle
ColoredRectangle.prototype = new Rectangle( );
ColoredRectangle.prototype.constructor = ColoredRectangle;
ColoredRectangle.prototype.superclass = Rectangle;

// And borrow the methods of Colored for our new class
borrowMethods(Colored, ColoredRectangle);


[quote]Although any kind of strict analogy is impossible, you can think of this as a kind of multiple inheritance. Since the ColoredRectangle class borrows the methods of Colored, instances of ColoredRectangle can be considered instances of Colored as well.[/quote]

通过在子类的构造函数中调用另一个父类的构造函数,并且借用那个父类的所有方法,就实现了我们的目的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值