欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-15 13:46:22
...

继承是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 object(obj){
    function temp(){};
    tem.prototype = obj;
    return new temp;
}

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

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上创建不必要的属性,于此同时,原型链还能保持不变。因此寄生组合式继承是引用类型最理想的继承范式。