面向对象实现继承的几种方法

1、原型链

1.1、构造函数和原型和实例之间的关系

每个构造函数都有一个原型对象,原型对象又有一个指向构造函数的指针,实例又有一个指向原型对象的指针。

1.2、原型链的概念

假如我们让原型对象等于另一个类型的实例,类型我理解为不同的构造函数,此时的原型对象将包含另一个原型对象的指针,相应的,另一个原型中也包含一个指向另一个构造函数的指针,假如,另一个原型是又另外一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。

function SuperType(){
    this.property=true;
}
SuperType.prototype.getSuperValue=function(){
    console.log(this.property);
}
function SubType(){
    this.subProperty=false;
}
SubType.prototype=new SuperType();   //继承了SuperType
SubType.prototype.constructor=SubType;
SubType.prototype.getSubValue=function(){
        console.log(this.subProperty);
}
var o = new SubType();
o.getSuperValue();   //true

以上代码定义了2个类型,SuperType和SubType,每个类型分别有一个类型和一个方法,他们的主要区别是SubType继承了SuperType,而继承是通过创建SuperType的实例并将该实例赋给SubType的原型实现的。实现的本质是重写原型对象,代之以一个新类型的实例,原来存在于SuperType的实例的属性和方法,现在也存在于SubType.prototype中了。

注意:加背景那一行在继承了SuperType类型之后,立马更正了SubType的原型的constructor属性,因为,此时constructor属性已经指向了SuperType。

问题:原型链虽然很强大,可以实现继承,但面对引用类型值的原型,有一个问题。

function SuperType(){
    this.colors=["red","blue","green"];
}
function SubType(){

}
SubType.prototype=new SuperType();
var o1=new SubType();
o1.colors.push("pink");
console.log(o1.colors);   //["red", "blue", "green", "pink"]
var o2=new SubType();
console.log(o2.colors);   //["red", "blue", "green", "pink"]

以上代码,superType构造函数定义了一个colors属性,该属性包含一个数组(引用类型值),SuperType的每个实例都会有各自包含数组的colors数组。当SubType 通过原型链继承了SuperType 之后,SubType.prototype 就变成了SuperType 的一个实例,因此它也拥有了一个它自己的colors 属性,就跟专门创建了一个SubType.prototype.colors 属性一样。但结果是什么呢?结果是SubType 的所有实例都会共享这一个colors 属性。

2、借用构造函数----伪造对象----经典继承

由于原型中包含引用类型的值有问题,又有一种新的方法出来了,叫做借用构造函数,或者伪造对象,或者经典继承,在子类型构造函数的内部调用超类型构造函数。

function SuperType(){
    this.colors=["red","green"];
}
SuperType.prototype.sayHi=function(){
    alert("hello~");
}
function SubType(){
    SuperType.call(this);
}
var o=new SubType();
o.colors.push("pink");
console.log(o.colors);    //["red", "green", "pink"]
var o1=new SubType();  
console.log(o1.colors);   //["red", "green"]
o1.sayHi();         //报错

以上代码中加背景的那一行调用了超类型的构造函数,通过使用call方法,我们实际上是在(未来将要)新创建的SubType实例的环境下调用了SuperType构造函数,这样的话,SubType的每个实例都会具有自己的colors属性的副本了。

2.1、借用构造函数的优势-----传递参数

相对于原型链来说,借用构造函数有一个优势就是,可以传递参数,即可以在子类型构造函数中向超类型构造函数传递参数。

function SuperType(name){
    this.name=name;
}
function SubType(){
    SuperType.call(this,"张三");
    this.age=24;
}
var o=new SubType();
alert(o.name);   //张三
alert(o.age);   //24

2.2、借用构造函数的问题

如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题----方法都在构造函数中定义,因此函数的复用也就无从谈起了,而且在超类型的原型中定义的方法,对子类型而言也是不可见的。

3、组合继承----伪经典继承----较为常用的继承方法

组合继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式,思路是使用原型链实现原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承,这样的话,即通过在原型上定义方法实现了函数的复用,又能保证每个实例都有他自己的属性。

function SuperType(name){
    this.name=name;
    this.colors=["red","green"];
}
SuperType.prototype.sayName=function(){
    console.log(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);  //继承了属性
    this.age=age;
}
SubType.prototype=new SuperType();  //继承了方法
SubType.prototype.constructor=SubType;   //将SubType的原型的constructor属性指回SubType
SubType.prototype.sayAge=function(){
    console.log(this.age);
}

var o=new SubType("张三",23);
o.colors.push("pink");
console.log(o.colors);  //["red", "green", "pink"]
o.sayName();  //张三
o.sayAge();   //23

var o1=new SubType("李四",25);
console.log(o1.colors);  //["red", "green"]
o1.sayName();  //李四
o1.sayAge();   //25

以上代码中SuperType定义了2个属性name和colors,SuperType的原型定义了一个方法sayName(),SunType构造函数在调用SuperType构造函数时传入了name参数,紧接着又定义了它自己的属性age,然后,将SuperType的实例赋值给SubType的原型,然后,又在该新原型上定义了方法sayAge,这样一来,就可以让俩个不同的SubType实例既分别拥有自己的属性,包括colors属性,又可以使用相同的方法了,组合继承是javascript中最常用的继承模式。instanceof 和isPrototypeOf()也能够用于识别基于组合继承创建的对象。

4、原型式继承

这种方法不使用构造函数实现继承,由json格式的发明人Douglas Crockford,提出了一个object()函数。

function object(o){
    function F(){};
    F.prototype=o;
    return new F();
}

以上代码,在object函数的内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。

function object(o){
    function F(){};
    F.prototype=o;
    return new F();
}
var person={
    name:"张三",
    colors:["red","green"]
}
var o1=object(person);
o1.name="李四";
o1.colors.push("pink");
console.log(o1.colors);     //["red", "green", "pink"]
        
var o2=object(person);
console.log(o2.colors);     //["red", "green", "pink"]

这种原型式继承,要求你必须有一个对象作为另一个对象的基础,如果有那么一个对象的话,可以把它传递给object函数,然后再根据具体需求对得到的对象加以修改即可。

ECMAScript 5,通过新增Object.create()方法规范化了原型式继承,这个方法接收2个参数,一个用作新对象原型的对象,和(可选的)一个为新对象定义额外属性的对象,在传入一个参数的情况下,Object.create()和上面的object()方法的行为相同。

var Chinese={
    nation:"中国"
}
var Doctor={
    job:"医生"
}
var o=Object.create(Chinese);
o.job="医生";

console.log(o.nation);    //中国

Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。例如:

var Chinese={
    nation:"中国"
}
var Doctor={
    job:"医生"
}
var o=Object.create(Chinese,{
    job:{
        value:"医生"
    }
});

console.log(o.job);    //医生

支持Object.create()方法的浏览器有IE9+、Firefox 4+、Safari 5+、Opera 12+和Chrome。

只想让一个对象和另一个对象保持类似的情况下,原型式继承是可以的。

5、寄生式继承

寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像是真的是它做了所有工作一样返回对象。

function Object(o){
    function F(){}
    F.prototype=o;
    return new F();
}
function createAnother(original){
    var clone=Object(original);
    clone.sayHi=function(){    //以某种方式来增强这个对象
        console.log("hi~");
    }
    return clone;
}

var person={
    name:"张三",
    colors:["red","green"]
}

var anotherPerson=createAnother(person);
anotherPerson.sayHi();     //hi~

以上例子中的代码,基于person返回了一个新对象,anotherPerson,新对象不仅具有person的所有属性和方法,还有属于自己的sayHi方法。在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。前面示范继承模式时使用的object()函数不是必需的;任何能够返回新对象的函数都适用于此模式。

6、继承组合式继承----最为理想的继承方法

组合继承是javascript最常用的继承模式,但是,它最大的问题就是无论什么情况下,都会调用2次超类型构造函数,一次是在创建子类型原型的时候,另一次是在子类型构造函数内部,虽然子类型最终会包含超类型对象的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性,回顾一下组合继承。

function SuperType(name){
    this.name=name;
    this.colors=["red","green"];
}
SuperType.prototype.sayName=function(){
    console.log(this.name);
}

function SubType(name,age){
    SuperType.call(this,name);   //第二次调用了SuperType
    this.age=age;
}
SubType.prototype=new SuperType();  //第一次调用了SuperType
SubType.prototype.sayAge=function(){
    console.log(this.age);
}
var o1=new SubType("张三",23);
o1.colors.push("pink");
console.log(o1.colors);    //["red", "green", "pink"]

var o2=new SubType("李四",25);  
console.log(o2.colors);   //["red", "green"]

以上代码,第一次调用SuperType构造函数时,SubType.prototype会得到俩个属性,name和colors,它们都是SuperType的实例属性,只不过现在位于SubType的原型中,当调用SubType的构造函数时,又会调用一次SuperType构造函数,这一次,又在新对象上创建了实例属性,name和colors,于是,这俩个实例属性就屏蔽了原型中的俩个同名属性。

寄生组合式继承的基本模式如下:

function inheritPrototype(SubType,SuperType){
    var prototype=Object(SuperType.prototype);  //创建对象
    prototype.constructor=SubType;    //增强对象
    SubType.prototype=prototype;    //指定对象
}

以上自定义函数中,接收2个参数,子类型构造函数和超类型构造函数,在函数内部第一步,创建超类型原型的一个副本,第二步是为创建的副本添加constructor属性,从而弥补因重写原型而失去的默认的constructor属性,最后一步,将新创建的对象(即副本)赋值给子类型的原型,以下,将这个函数运用于前面的例子:

function Object(o){
    function F(){}
    F.prototype=o;
    return new F();
}
function inheritPrototype(SubType,SuperType){
    var prototype=Object(SuperType.prototype);  //创建对象
    prototype.constructor=SubType;    //增强对象
    SubType.prototype=prototype;    //指定对象
}

function SuperType(name){
    this.name=name;
    this.colors=["red","green"];
}
SuperType.prototype.sayName=function(){
    console.log(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age=age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge=function(){
    console.log(this.age);
}

var o1=new SubType("张三",23);
o1.colors.push("pink");
console.log(o1.colors);   //["red", "green", "pink"]
        
var o2=new SubType("李四",25);
console.log(o2.colors);    //["red", "green"]

以上代码,高效率体现在它只调用了一次SuperType构造函数,并且,因此避免了在SubType.prototype上创建不必要的,多余的属性,现在SubType.prototype上面只有sayName方法和sayAge方法了,寄生组合式继承是引用类型最为理想的继承方式。

原文地址:https://www.cnblogs.com/xlj-code/p/8342318.html