JavaScript面向对象的程序设计之继承

继承是OO语言中一个最为人津津乐道的概念,许多OO语言都支持两种继承方式:接口继承和实现继承。ECMAScript只支持实现继承,而实现继承主要是依靠原型链实现的。

1、原型链方法

基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法。
实现方法:通过创建超类(父类)的实例,并将该实例赋值给子类的原型实现的。本质上是重写原型对象,代之一个新类型的实例。

function SuperType(){
    this.colors = ['red','blue','green'];
}
function SubType(){
}
//继承了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';

问题:最主要的问题主要在包含引用类型值的原型。一个实例的改变会导致原型上内容的改变,子类的所有实例都会共享超类的属性。第二个问题,在创建子类型的实例时,不能向超类型的构造函数中传递参数。

2、借用构造函数

基本思想:在子类型构造函数的内部调用超类型构造函数,用call()和apply()方法改变this的指向。
优点:解决原型中包含引用值所带来的问题;可以在子类型构造函数中向超类型构造函数传递参数。

function SUperType(){
    this.colors = ['red','blue','green'];
}
function SubType(){
    //继承了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';
//传参
function SuperType(){
    this.name = name;
}
function SubType(){
    //继承了SuperType,同时还传递了参数
    SuperType.call(this,'zhangsan');
    //实例属性
    this.age = 29;
}
var instance = new SubType();
alert(instance.name);//'zhangsan'
alert(instance.age);//29

问题:借用构造函数也是有一定问题的,所有的方法都是在构造函数中定义,函数复用就无从谈起了。而且超类原型中定义的方法对子类是不可见的,结果所有的类型就只能使用构造函数模式了。

3、组合继承

组合继承是将原型链和借用构造函数组合到一起的一种继承模式,是JavaScript中最常用的继承模式。
主要思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现函数复用,又能保证每个实例都有它自己的属性。

function SuperTye(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}
function SubType(name,age){
    //继承属性
    SuperType.call(this,name);//第二次调用SuperType()
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();//第一次调用SuperType()
SubType.prototype.sayAge = function(){
    alert(this.age);
}
var instance1 = new SubType('zhangsan','25');
instance1.colors.push('black');
alert(instance1.colors);//'red,blue,green,black'
instance1.sayName();//'zhangsan'
instance1.sayAge();//'25';
var instance2 = new SubType('lisi','27');
alert(instance2.colors);//'red,blue,green'
instance2.sayName;//'lisi';
instance2.sayAge;//'27';

问题:但是这种方法也不是完全没有缺点的,可以看出在这个过程中超类使用了两次,一次在创建子类原型的时候,另一次是在子类构造函数内部,但是使用过程中,子类的实例属性会屏蔽原型属性,也就是说某些原型属性其实是用不上的,这造成了内存的浪费。

4、原型式继承

这种方法其实没有严格意义上的构造函数,主体思想是基于已有的对象并借助原型构建新的对象。

function inherit(obj){
    function temp(){};
    tem.prototype = obj;
    return new temp;
}

参照上述主体思想,在inherit函数内部先创建一个临时的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时原型的新实例。本质上讲,这就是对传入对象进行了一次浅复制。

5、寄生式继承

创建一个仅用于封装继承过程的函数,该函数的内部以别的方法来增强对象,最后再返回对象。

function createAnother(original){
    var clone = object(original);
    clone.sayHi = function(){
        alert('Hi');
    }
    return clone;
}

上述例子中,接收的参数就是将要作为新对象基础的对象,最后再给clone对象增加新的方法。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承是一种有用的模式。缺点主要在于原型对象和实例有重名的属性。

6、寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混成形式来继承方法;基本思路就是不必为了指定子类型的原型而调用超类的构造函数,我们所需要的其实就是一个超类型的副本。简单说就是使用寄生式来继承超类的原型,再将结果给子类的原型。

function inhetitPrototype(subType,superType){
    var prototype = object(subType.prototype);//创建对象
    prototype.constructor = subType;//增强对象
    subType.prototype = prototype;//指定对象
}

上述函数接收两个参数,分别为子类构造函数和超类构造函数。函数内部第一步常见超类原型的一个副本,第二步为创建的副本添加constructer属性,弥补一下因为重写原型而失去的默认的constructer属性,第三部将新创建的对象赋给子类的原型。

function SuperTye(name){
    this.name = name;
    this.colors = ['red','blue','green'];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);//调用一次SuperType()
    this.age = age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
}

相对于组合继承,这个方法最大的优势就在于它只调用了一次SuperType构造函数,因此避免了在SubType.prototype上创建不必要的属性,于此同时,原型链还能保持不变。因此寄生组合式继承是引用类型最理想的继承范式。

猜你喜欢

转载自blog.csdn.net/xuedan1992/article/details/80929530