JavaScript面向對象---原型鏈繼承

最近一直在研究js面向對象,原型鏈繼承是一個難點,下面是我對繼承的理解
如下文章借鑑自CSDN季詩筱的博客數組


原型鏈繼承的基本概念:

ES中描述了原型鏈的概念,並將原型鏈做爲實現繼承的主要方法;
基本思想:利用一個引用類型繼承另外一個引用類型的屬性和方法:
簡單回顧下: 構造函數 -- 原型 -- 實例 三者之間的關係
構造函數:function Person(){}
每一個構造函數都有一個原型對象(Person.prototype),
原型對象都包含一個指向構造函數的指針(constructor),
(其實原型對象也是一個對象,也有一個 __proto__ 指針,指向他所繼承的對象)
而實例都包含着一個指向圓形對象的內部指針([[prototye]] 又稱__proto__);
每一個實例也有一個constructor屬性默認調用原型對象的constructor屬性(!!!)函數

圖片描述

原型鏈繼承的核心
  • 讓原型對象等於另外一個構造函數的實例, 顯然,此時的原型對象將包含一個指向另外一個原型對象的指針([[prototype]]),從而擁有該原型對象的屬性和方法this

  • 另外一個原型對象中也包含着指向另外一個構造函數的指針。那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念!spa

  • 如上如所示,這種關係直到 當某個原型對象的 contructor屬性指向 Object 爲止.net


1.原型鏈繼承基本模式:

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

function SubType(){
    this.subproperty = false;
}
//繼承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function(){
    return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue());  //true

以上代碼定義了兩個類型:SuperType 和 SubType.
每一個類型分別有一個屬性和方法
他們的主要區別是SubType繼承了SuperType,而繼承是經過建立SuperType的實例,並將該實例賦值給SubType.prototype實現的
實現的本質是重寫原型對象,代之以一個新類型的實例
換句話說,原來存在於SuberType的實例中的全部屬性和方法,如今存在於SubType.prototype中了。
在確立了繼承關係以後,咱們給SubType.prototype添加了一個方法,,這樣就在繼承了SuperType的屬性和方法的基礎上又添加了一個新方法。
這個例子的實例以及構造函數和原型之間的關係以下圖所示
圖:
clipboard.pngprototype

在上面的代碼中,咱們沒有使用SubType默認提供的原型,而是給他換了一個新原型
這個原型就是SuperType的實例
因而,新原型不只有做爲一個SuperType的實例所擁有的所有屬性和方法,並且內部還有一個指針指向了SuperType的原型
最終結果是這樣的:instace指向SubType的原型,SubType的原型又指向SuperType的原型指針


在經過原型鏈繼承的狀況下,搜索過程就得以沿着原型鏈繼續向上。就拿上面的例子來講,調用instance.getSuperValue()會經歷三個步驟:
1.搜索實例
2.搜索SubType.prototype
3.搜索SuperType.prototype
最後一步纔會找到該方法,再找不到該屬性或方法的狀況下,搜索過程老是要一環一環地前行到原型鏈末端纔會停下來.
別忘記默認原型Object
事實上前面例子中展現的原型鏈還少一環,咱們知道,全部引用類型都默認繼承了Object,而這個繼承也是經過原型鏈實現的。
你們記住,全部函數的默認原型都是Object的實例,所以默認原型內部都會包含一個指針,指向Object.prototype。這也正是全部自定義類型都會繼承toString()等默認方法的根本緣由.code


原型鏈存在的問題

原型鏈雖然很強大,能夠用它實現繼承,但也存在一些問題。對象

  1. 其中,最主要的問題來自包含引用類型值的原型。
    想必你們還記得,咱們前面介紹過包含引用類型值的屬性會被全部實例共享;而這也正是爲何要在構造函數中,而不是在原型對象中定義屬性的緣由。blog

在經過原型來實現繼承時,原型實際上會變成另外一個類型的實例。因而原先的實例屬性也就瓜熟蒂落地變成了如今的原型屬性了.
例子說明問題:

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

}
SubType.prototype = new SuperType();    // 繼承了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屬性,該屬性包括一個數組(引用類型值)。SuperType的每一個實例都會有各自包含本身數組的colors屬性。當SubType經過原型鏈繼承了SuperType以後,SubType.prototype就變成了SuperType的一個實例,所以他也擁有了一個他本身的colors屬性-----就跟專門建立了一個SubType.prototype.colors屬性同樣。
但結果是什麼呢? 全部實例都會共享這個colors屬性!!!


問題1:結果是SubType的全部實例都會共享這一個colors屬性。而咱們對instance1.colors的修改可以經過instance2.colors反映出來,就已經充分證實這一點了

問題2:在建立自定義類型的時候,不能向超類型的構造函數中傳遞參數。
實際上,應該說是沒有辦法在不影響全部實例的狀況下,給超類型構造函數傳遞參數。
有鑑於此,在加上前面剛剛討論的因爲原型中所包含引用類型值所帶來的問題,實踐中中不多單獨使用原型鏈


2.原型鏈繼承缺陷的解決方法(紅寶書)

1.借用構造函數
2.組合式繼承
3.原型式繼承
4.寄生式繼承
5.寄生組合式繼承


這裏來談一下最經常使用的組合式繼承和寄生組合式繼承

組合式繼承:

組合繼承,有時候也叫作經典繼承,指的是將原型鏈和借用構造函數的技術組合到一塊兒,從而發揮兩者之長的一種繼承模式。
其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例的屬性的繼承
請看例子:

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);   //繼承屬性, 第二次調用SuperType
    this.age = age;
}
//繼承方法
SubType.prototype = new SuperType();       //第一次調用SuperType
SubType.prototype.constructor = SubType(); // 至關重要,此處
SubType.prototype.sayAge = function(){
    alert(this.age);
}
var instance1 = new SubType("leo",29);
instance1.colors.push("black");
alert(instance1.colors);  // r,b,g,b
instance1.sayName();      // leo
instance1.sayAge();       // 29

var instance2 = new SubType("lck",34);
alert(instance2.colors);  // r,b,g
instance2.sayName();      // lck
instance2.sayAge();       // 34

在例子中,SuperType構造函數定義了兩個屬性:name 和 colors。SuperType的原型定義了一個方法sayName()
SubType構造函數在調用SuperType構造函數傳入了name參數,緊接着又定義了他本身的屬性age,而後,將SuperType的實例賦值給SubType的原型,而後又在該新原型上定義了方法sayAge()方法
這樣一來,就可讓兩個不一樣的SubType實例既分別擁有本身屬性和公共的colors屬性,又可使用相同的方法
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了他們的優勢,成爲js中最經常使用的繼承模式。
並且 instanceof和isPrototypeOf()也可以用於識別基於組合繼承建立的對象
**另外說一下:
clipboard.png
1.任何一個Prototype對象都有一個constructor指針,指向它的構造函數
2.每一個實例中也會有一個constructor指針,這個指針默認調用Prototype對象的constructor屬性。
結果:當替換了子類的原型以後,即 SubType.prototype = new SuperType()以後,
SubType.prototype.constructor 就指向了SuperType(),
SubType的實例的constructor也指向了SuperType(),這就出現問題了。

由於這形成了繼承鏈的紊亂,由於SubType的實例是由SubType構造函數建立的,如今其constructor屬性卻指向了SuperType,爲了不這一現象,就必須在替換prototype對象以後,爲新的prototype對象加上constructor屬性,使其指向原來的構造函數。


組合式繼承的缺點
組合繼承最大的問題就是不管在什麼狀況下,都會兩次調用超類型構造函數;
一次是在建立子類型的原型的時候,
另外一次是在子類型構造函數內部。
沒錯子類型最終會包含超類型對象的所有實例屬性,但咱們不得不在調用子類構造函數時重寫這些屬性


寄生組合式繼承

此種模式解決了組合式繼承的缺點
原理:經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。
思路:沒必要爲了指定子類的原型而調用超類型的構造函數,咱們所須要的無非就是超類型原型的一個副本而已

本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。

寄生式組合繼承的基本模式以下所示:

function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);    // 建立對象
    prototype.constructor = subType;                // 加強對象
    subType.prototype = prototype;                  // 指定對象
}

這個示例中的inheritPrototype()函數實現了寄生式組合繼承的最簡單形式。
這個函數接收兩個參數:子類型構造函數和超類型構造函數。
在函數內部:
第一步:是建立超類型原型的一個副本
第二步:爲建立的副本添加constructor屬性,從而彌補因失去原型而失去的默認的constructor屬性
第三步:將建立的對象(即副本)賦值給子類型的原型。
這樣,咱們就能夠調用inherit-Protoype()函數的語句,去替換前面例中爲了子類型原型賦值的語句了

實例以下:
//藉助原型能夠基於已有對象建立新對象
function object(o){
    var F = function(){};    // 建立一個空對象
    F.prototype = o;
    return new F();          //返回出一個實例對象
}
//寄生式組合繼承
function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);  //建立父構造函數實例對象副本
    prototype.constructor = subType;    // 重寫將子類原型對象的constructor屬性
    subType.prototype = prototype;     // 父類實例賦值給子類原型
};         

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);
}; 
var instance1 = new SubType("lck",29);  console.log(instance1.name,instance1.age,instance1.colors); // lck,29,r,b,g
instance1.sayName();  // lck
instance1.sayAge()   // 29

優勢:
這個例子的高效率體如今他只調用了一次SuperType構造函數,
而且所以避免了在SubType.prototype上面建立沒必要要的,多餘的屬性。
與此同時,原型鏈還能保持不變
所以,還能正常使用instanceof 和 isPrototypeOf()。
開發人員廣泛認爲寄生式組合式繼承是引用類型最理想的繼承範式

相關文章
相關標籤/搜索