JavaScript中对象的继承

继承分为接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。
由于函数没有签名,因此在ECMAscript中不支持接口继承,只支持实现继承。
ECMAScript中描述了原型链的概念,并将其作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。

实现原型链的基本模式

其代码大致为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function SuperType(){
this.property = true;
}

SuperType.prototype.getSuperValue = function(){
return this.property;
};

function SubType(){
this.subproperty = false;
}

//inherit from SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true

这种模式将SubType的prototype设为SuperType的实例,这样SubType的实例就继承了所有SuperType的属性和方法。

javascriptObjectInherit1.png

使用instanceof运算符和isPrototypeOf()方法可以辨别原型和方法之间的关系,这两种方法都会对_原型链上所有_的原型返回true。

1
2
3
4
5
6
7
8
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

基本模式的问题

基本模式有两个问题:

  • 首先,因为SubType的原型为SuperType的实例,因此SuperType的实例对象和方法都会变成SubType的原型对象和方法。而引用类型的值在原型中会被所有实例所共享。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function SuperType(){
    this.colors = [“red”, “blue”, “green”];
    }

    function SubType(){
    }

    //inherit from SuperType
    SubType.prototype = new SuperType();

    var instance1 = new SubType();
    instance1.colors.push(“black”);
    alert(instance1.colors); //”red,blue,green,black”
    var instance2 = new SubType();
    alert(instance2.colors); //”red,blue,green,black”

    在这个例子中,原本是SuperType实例属性的colors现在变成了SubType的原型属性,因此它会被所有SubType的实例所共享。

  • 第二个问题是如果SuperType的构造函数有参数,那么基本模式在创建SubType的实例时没有办法给SuperType的构造函数传递参数。
    可以使用两个方法来解决上面两个问题:偷窃构造函数原型继承

原型继承

2012年由Douglas Crockford(创建JSON的人)介绍的原型继承用于解决实例属性变成原型属性的问题。
方法是创建一个空函数F(),作为中介,将他的实例代替SuperType的实例来作为SubType的原型。这样F.prototype中就不再会包含SuperType的实例属性。
而通过将F.prototype指向SuperType.prototype又使他继承了SuperType的原型属性。

1
2
3
4
5
6
7
8
9
10
11
function object(o){
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //create object
prototype.constructor = subType; //augment object
subType.prototype = prototype; //assign object
}

javascriptObjectInherit2.jpg

但是原型继承仅仅是继承了SuperType.prototype的属性,需要下面的方法来继承SuperType的实例属性。

偷窃构造函数

基本思想是在subtype的构造函数中调用supertype的构造函数。可以使用apply()或者call()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
function SuperType(){
this.colors = [“red”, “blue”, “green”];
}
function SubType(){
//inherit from SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push(“black”);
alert(instance1.colors); //”red,blue,green,black”
var instance2 = new SubType();
alert(instance2.colors); //”red,blue,green”

.call方法使得SuperType构造函数在SubType构造函数域中运行,使得SubType构造函数得到了SuperType构造函数的属性。从而继承了实例属性。

最优方式

最后,通过偷窃构造函数 和 原型继承两种方式结合,得到最优的对象继承方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function SuperType(name){
this.name = name;
this.colors = [“red”, “blue”, “green”];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};

javascriptObjectInherit3.jpg