js對(duì)象——繼承(2)

繼承分為接口繼承和實(shí)現(xiàn)繼承,但由于接口繼承只繼承方法簽名,而函數(shù)沒(méi)有簽名,所以ECMAScript只支持實(shí)現(xiàn)繼承(實(shí)現(xiàn)繼承繼承實(shí)際的方法)。實(shí)現(xiàn)繼承主要是依靠原型鏈來(lái)實(shí)現(xiàn)的。

一. 原型鏈

1.基本思想:利用原型讓一個(gè)引用類(lèi)型繼承另一個(gè)引用類(lèi)型的屬性和方法

回顧一下構(gòu)造函數(shù),原型,實(shí)例的關(guān)系:每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象,原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針,而實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針。那么如果讓原型對(duì)象等于另一個(gè)類(lèi)型的實(shí)例,結(jié)果會(huì)怎樣呢?顯然,此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)的,另一個(gè)原型中也包含著指向另一個(gè)構(gòu)造函數(shù)的指針。


示例圖

實(shí)現(xiàn)原型鏈有一種基本模式,其代碼大致如下

function SuperType(){
  this.supername=true;
}
SuperType.prototype.getSuperValue=function(){
  return this.supername;
};
function SubType(){
  this.subname=false;
}
//繼承了SuperType
SubType.prototype=new SuperType();
//添加新方法
SubType.prototype.getSubValue=function(){
  return this.subname;
};

var instance=new SubType();
alert(instance.getSuperValue())  //true
  • 構(gòu)造函數(shù),實(shí)例,原型之間具體關(guān)系圖及最終結(jié)果的詳細(xì)說(shuō)明
    構(gòu)造函數(shù),實(shí)例,原型之間具體關(guān)系圖

(1)instance指向SubType的原型,SubType的原型又指向SuperType的原型。
(2)getSuperValue()方法仍然在SuperType.prototype中,但prototype則位于SubType.prototype中,這是系因?yàn)閜rototype是一個(gè)實(shí)例屬性( 此時(shí)SubType.prototype=new SuperType() ),而getSuperValue()是一個(gè)原型方法。
(3)此時(shí)的instance.constructor指向的是SuperType,這是因?yàn)樵瓉?lái)的SubType.prototype中的constructor被重寫(xiě)了的緣故

  • 默認(rèn)的原型
    所有引用類(lèi)型默認(rèn)都繼承了Object,而這個(gè)繼承也是通過(guò)原型鏈實(shí)現(xiàn)的。因此,所有默認(rèn)原型都是Object的實(shí)例,Object對(duì)象處于原型鏈的頂端
  • 判斷原型和實(shí)例之間的關(guān)系

(1)instanceof
只要用這個(gè)操作符來(lái)測(cè)試實(shí)例與原型鏈中出現(xiàn)過(guò)的構(gòu)造函數(shù),結(jié)果都會(huì)返回rue

var instance=new SubType();
alert(instance instanceof Object);    //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType);   //true

(2)isPrototypeOf()
只要是原型鏈中出現(xiàn)過(guò)的原型,都可以說(shuō)是該原型鏈所派生的實(shí)例的原型,返回true。

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

2.添加方法與重寫(xiě)超類(lèi)型中的方法

(1)給原型添加方法的代碼一定要放在替換原型的語(yǔ)句之后,即繼承,再給prototype添加方法。

//繼承了SuperType
SubType.prototype=new SuperType();
//添加新方法
SubType.prototype.getSubValue=function(){
  return this.subname;
};
//重寫(xiě)超類(lèi)型中的方法
SubType.prototype.getSuperValue=function(){
  return 1;
}

var instance=new SubType();
alert(instance.getSuperValue());  // 1

重寫(xiě)了 getSuperValue()方法后,通過(guò)SubType的實(shí)例調(diào)用這個(gè)方法時(shí),調(diào)用的是這個(gè)重新定義的getSuperValue()方法,返回1,但是通過(guò)SuperType的實(shí)例調(diào)用這個(gè)方法時(shí),調(diào)用的還是原來(lái)的getSuperValue()。
(2)不能用對(duì)象字面量創(chuàng)建原型方法。
如果用對(duì)象字面量創(chuàng)建原型方法,就會(huì)重寫(xiě)原型鏈

//繼承了SuperType
SubType.prototype=new SuperType();
//用對(duì)象字面量添加方法
SubType.prototype={
  getSubValue:function(){
    return this.subname;
  };
  someOtherMethod:funtion(){
    return "other medthod!";
  }
};

var instance=new SubType();
alert(instance.getSuperValue());  //出現(xiàn)error

由于現(xiàn)在的原型包含的是一個(gè)object實(shí)例,而非SuperType的實(shí)例,因此我們?cè)O(shè)想中的原型鏈已經(jīng)被切斷——SubType和SuperType之間已經(jīng)沒(méi)有任何關(guān)系。

3.原型鏈的問(wèn)題

牽一發(fā)而動(dòng)全身 : 原先的實(shí)例屬性變成了現(xiàn)在的原型屬性,只要有一個(gè)A對(duì)象的實(shí)例改動(dòng)繼承過(guò)來(lái)的屬性,就會(huì)影響到所有的A對(duì)象實(shí)例。從下面這個(gè)例子就可以看出

function SuperType(){
  this.colors=["red","blue"];
}
function SubType(){}
//繼承了SuperType
SubType.prototype=new SuperType();

//instance_a為第一個(gè)SubType對(duì)象
var instance_a=new SubType();
instance_a.colors.push("green");
alert(instance_a.colors);   //red,blue,green
//instance_b為第二個(gè)SubTypee對(duì)象
var instance_b=new SubType();
alert(instance_b.colors);   //red,blue,green

當(dāng)SubType通過(guò)原型鏈繼承了SuperType之后,SubType.prototype就變成了SuperType的一個(gè)實(shí)例,因此它也擁有了一個(gè)它自己的colors屬性。SubType的所有實(shí)例都會(huì)共享這一個(gè)colors屬性。
解決方案見(jiàn)下↓↓↓↓↓↓↓↓↓↓↓

二 .借用構(gòu)造函數(shù)

基本思想:在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型構(gòu)造函數(shù)。

1.使用apply()和call()方法

function SuperType(){
  this.colors=["red","blue"];
}
function SubType(){
  //繼承了SuperType
  SuperType.call(this);
}

var instance_a=new SubType();
instance_a.colors.push("green");
alert(instance_a.colors); //red,blue,green

var instance_b=new SubType();
alert(instance_b.colors);  //red,blue

通過(guò)使用call方法(也可以是apply),我們實(shí)際上是在將來(lái)新建的SubType實(shí)例的環(huán)境下調(diào)用了SuperType構(gòu)造函數(shù)。這樣一來(lái),就會(huì)在新SubType對(duì)象上執(zhí)行SuperType()函數(shù)中定義的所有對(duì)象初始化代碼。因此SubType的每一個(gè)實(shí)例就會(huì)具有自己的colors屬性的副本了。

2.傳遞參數(shù)

相對(duì)于原型鏈,借用構(gòu)造函數(shù)有一個(gè)很大的優(yōu)勢(shì),即可以在子類(lèi)型構(gòu)造函數(shù)中向超類(lèi)型構(gòu)造函數(shù)傳遞參數(shù)。如下:

function SuperType(name){
  this.name=name;
}
function SubType(){
  //繼承了SuperType,同時(shí)還傳遞了參數(shù)
  SuperType.call(this,"Jim");
  this.age=18;
}

var instance=new SubType();
alert(instance.name); //Jim
alert(instance.age);  //18

3.借用構(gòu)造函數(shù)的問(wèn)題

三 .組合繼承(常用)

組合繼承有時(shí)候也稱(chēng)經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合在一起。

基本思想:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。

function SuperType(name){
  this.name=name;
  this.colors=["red","blue"];
}
SuperType.prototype.sayName=function(){
  alert(this.name);
}
function SubType(name,age){
  SuperType.call(this,name);  //繼承屬性
  this.age=age;
}
SubType.prototype=new SuperType();  //繼承方法
SubType.prototype.sayAge=function(){
  alert(this.age);
}

var instance_a=new SubType("Jim",18);
instance_a.colors.push("green");
alert(instance_a.colors)    //red,blue,green
instance_a.sayName();       //Jim
instance_a.sayAge();        //18

var instance_b=new SubType("Amy",19);
alert(instance_b.colors);   //red,blue

使用instanceof和isPrototypeOf()識(shí)別基于組合繼承創(chuàng)建的對(duì)象
缺點(diǎn):最大的問(wèn)題是無(wú)論什么情況下,都會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù):一次是在創(chuàng)建子類(lèi)型原型的時(shí)候(SubType.prototype=new SuperType();),另一次是在子類(lèi)型構(gòu)造函數(shù)內(nèi)部(SuperType.call(this,name); //繼承屬性)。

四 .原型式繼承

當(dāng)沒(méi)有必要興師動(dòng)眾的創(chuàng)建構(gòu)造函數(shù),而只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持相似的情況下,可以選擇原型式繼承

基本思想:借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類(lèi)型。

1.基本用法

看看下面的例子

var person={
    name:"Jim",
    friends:["Amy","Tom"]
  };
  var person_a=object(person);   //Jim ,,此處請(qǐng)用Object.create()代替object(),因?yàn)閛bject自ES5后被Object.create取代,所以會(huì)出現(xiàn)錯(cuò)誤。
  alert(person_a.name);
  person_a.friends.push("Van");
  alert(person.friends);  //Amy,Tom,Jim
  alert(person_a.friends);  //Amy,Tom,Jim

注:object自ES5被更加規(guī)范的Object.create所取代,并且Object.create()與object()行為相同,稍后會(huì)講到Object.create
原型式繼承,要求你必須有一個(gè)對(duì)象可以作為另一個(gè)對(duì)象的基礎(chǔ)。找到作為基礎(chǔ)的對(duì)象(可以被修改),然后傳遞給object()函數(shù),再根據(jù)具體需求修改即可。

  • object()做了什么呢?
function object(o){
    function F(){}
    F.prototype=o;
    return new F();
  }

2.新增的Object.create()

ECMAScript5通過(guò)新增的Object.create()方法規(guī)范了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù),一個(gè)用作新對(duì)象原型的對(duì)象,另一個(gè)為新對(duì)象定義額外的屬性的對(duì)象。在傳入一個(gè)參數(shù)的情況下Object.create()=object()

var person={
    name:"Jim",
    friends:["Amy","Tom"]
  };
  var person_a=Object.create(person,{
    name:{
      value:"Greg",
    }
  });

  alert(person_a.name);   //Greg

五. 寄生式繼承

寄生式繼承與寄生構(gòu)造函數(shù)和工廠(chǎng)模式類(lèi)似,即創(chuàng)建一種僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)在內(nèi)部以某種方式來(lái)增強(qiáng)對(duì)象,最后再像是做了所有工作一樣返回對(duì)象。

//用于封裝繼承過(guò)程的函數(shù)createAnother
function createAnother(original){
    var clone=Object.create(original);  //通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
    clone.sayHi=function(){             //以某種方式來(lái)增強(qiáng)這個(gè)對(duì)象(為對(duì)象添加新方法)
      alert("Hi,"+this.name);
    };
    return clone;     //返回這個(gè)對(duì)象
  }
  //創(chuàng)建基礎(chǔ)的person對(duì)象
  var person={
    name:"Jim",
    friends:["Amy","Tom"]
  };
  
  var person_a=createAnother(person);
  person_a.sayHi();   // Hi,Jim

在主要考慮對(duì)象 而不是自定義類(lèi)型和夠愛(ài)函數(shù)的情況下,寄生式模式是一種有用的模式
缺點(diǎn):由于不能做到函數(shù)的復(fù)用而降低效率,這一點(diǎn)與構(gòu)造函數(shù)模式相似

六 . 寄生組合式繼承

前面有說(shuō)過(guò),最常用的繼承方式是組合繼承,但組合繼承的缺點(diǎn)是會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù)。寄生組合式繼承就解決了這一麻煩。
組合繼承兩次調(diào)用超類(lèi)型的地方在:
(1)創(chuàng)建子類(lèi)型原型 SubType.prototype=new SuperType(); : 繼承原型屬性和方法
(2)子類(lèi)型構(gòu)造函數(shù)內(nèi)部 SuperType.call(this); : 繼承實(shí)例屬性
寄生組合式繼承選擇丟棄(1)保留(2),用什么來(lái)繼承原型呢?使用寄生式繼承來(lái)繼承超類(lèi)型的原型。并且繼續(xù)用構(gòu)造函數(shù)來(lái)繼承實(shí)例屬性

1. 寄生組合式繼承的基本模式

function inheritPrototype(subType,superType){
    var prototype=Object.create(superType.prototype);  //創(chuàng)建對(duì)象
    prototype.constructor=subType;              //增強(qiáng)對(duì)象
    subType.prototype=prototype;                //指定對(duì)象
  }

這個(gè)函數(shù)接收兩個(gè)參數(shù):子類(lèi)型構(gòu)造函數(shù)和超類(lèi)型構(gòu)造函數(shù)。
在函數(shù)內(nèi)部:
(1)第一步:是建立超類(lèi)型原型副本
(2)大二步:為創(chuàng)建的副本添加constructor屬性,從而彌補(bǔ)因重寫(xiě)原型而失去的默認(rèn)constructor屬性。
(3)第三步:將新創(chuàng)建的對(duì)象賦值給子類(lèi)型的原型。

2.寄生組合式的使用

function inheritPrototype(subType,superType){
    var prototype=Object.create(superType.prototype);  //創(chuàng)建對(duì)象
    prototype.constructor=subType;              //增強(qiáng)對(duì)象
    subType.prototype=prototype;                //指定對(duì)象
  }

  function SuperType(name){
    this.name=name;
    this.colors=["red","blue"];
  }
  SuperType.prototype.sayName=function(){
    alert(this.name);
  }
  //定義SubType對(duì)象
  function SubType(name,age){
    SuperType.call(this,name);    //通過(guò)構(gòu)造函數(shù)繼承屬性
    this.age=age;
  }

  inheritPrototype(SubType,SuperType);    //通過(guò)原型鏈的混合形式繼承方法
  SubType.prototype.sayAge=function(){
    alert(this.age);
  }

  var sub=new SubType("Jim",18);
  sub.sayName();    //Jim
  sub.sayAge();     //18

這個(gè)例子的高效率體現(xiàn)在它只調(diào)用了一次SuperType構(gòu)造函數(shù)。開(kāi)發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類(lèi)型最理想的繼承范式。

總結(jié)

(1)原型鏈:JavaScript主要是通過(guò)原型鏈實(shí)現(xiàn)繼承。原型鏈的構(gòu)建是通過(guò)將一個(gè)類(lèi)型的實(shí)例實(shí)例復(fù)制給另一個(gè)構(gòu)造函數(shù)的原型實(shí)現(xiàn)的。這樣子類(lèi)型就可以訪(fǎng)問(wèn)超類(lèi)型的所有屬性和方法。但是原型鏈的問(wèn)題是,子類(lèi)型也可以修改超類(lèi)型的所有屬性和方法。
(2)借用構(gòu)造函數(shù):在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型的構(gòu)造函數(shù)。這樣每個(gè)實(shí)例都有自己的屬性,同時(shí)還能保證只使用構(gòu)造函數(shù)模式來(lái)定義類(lèi)型。但是這樣的話(huà),函數(shù)的復(fù)用也就無(wú)從談起了。
(3)組合繼承:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。缺點(diǎn)是至少調(diào)用了兩次超類(lèi)型。
(4)原型式繼承:可以在不必預(yù)先定義構(gòu)造函數(shù)的情況下實(shí)現(xiàn)繼承。復(fù)制得到的副本還可以得到進(jìn)一步改造。
(5)寄生式繼承:和原型式繼承很相似,但是在函數(shù)內(nèi)部增強(qiáng)了對(duì)象(添加方法等),最后返回對(duì)象
(6)寄生組合式繼承:集寄生式繼承和組合繼承的優(yōu)點(diǎn)于一身,是實(shí)現(xiàn)基于類(lèi)型繼承最有效的方式

---------------------------《JavaScript高級(jí)程序設(shè)計(jì)(第三版)》讀書(shū)筆記-------------------------------

附:相關(guān)文章

js對(duì)象——?jiǎng)?chuàng)建對(duì)象(1)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容