JS Pro-深入物件導向的程式設計之繼承的詳解

來源:互聯網
上載者:User

原型鏈(prototype chaining):

利用原型來繼承屬性和方法。回顧一下建構函式(constructor),原型對象(prototype)和執行個體(instance)的關係。每一個建構函式都有一個prototype屬性,該屬性指向一個prototype對象;prototype對象也有constructor屬性,指向該函數;而執行個體也有一個內部指標(__proto__)指向這個prototype對象。如果這個prototype對象是另外一個對象的執行個體會是怎樣的呢?這樣該prototype對象就包含一個指向另一個類型的指標,相應地,另一個原型中也包含著一個指向另一個建構函式的指標。

JS的繼承很簡單,就是把子類的prototype設為父類的一個(執行個體化)對象
複製代碼 代碼如下:
function SuperType(){
    this.property = true;
}

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

function SubType(){
    this.subproperty = false;
}

//inherit from SuperType
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

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

最終的結果:instance的__proto__指向SubType.prototype對象,而SubType.prototype對象的__proto__屬性又指向SuperType.prototype對象。getSuperValue()是一個方法,所以仍然存在於原型中,而property是一個執行個體屬性,所以現在存在於SubType.prototype這個執行個體中。  instance.constructor現在指向的是SuperType,這是由於SubType.prototype指向SuperType.prototype,而SuperType.prototype的constructor屬性指向SuperType函數,所以instance.constructor指向SuperType。

預設情況下,所有參考型別都繼承Object。這是因為所有函數的原型對象,預設都是Object的一個執行個體,所以內部prototype(__proto__)指向Object.Prototype。

原型和執行個體的關係:可以使用2種方法來確定原型與執行個體之間的關係。
- instancef操作符:使用該操作符來測試執行個體與原型鏈中出現過的建構函式,都會返回true
複製代碼 代碼如下:
alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

- isPrototypeOf()方法:只要是原型鏈中出現過的原型,都可以說是該原型鏈所派生的執行個體的原型。
複製代碼 代碼如下:
alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

給子類添加方法的注意點:我們有的時候會給子類添加方法,或者是重寫父類的某些方法。這個時候就要注意,這些方法必須在繼承後再定義。以下的例子裡,SubType在繼承SuperType後,我們給它添加了新的方法getSubValue(),而且重寫了getSuperValue()方法。對於後者,只有SubType的執行個體才會使用重寫的方法,SuperType的執行個體還是會使用原有的getSuperValue()方法。
複製代碼 代碼如下:
function SuperType(){
  this.property = true;
}
SuperType.prototype.getSuperValue = function(){
  return this.property;
};
function SubType(){
  t his.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
//new method
SubType.prototype.getSubValue = function (){
  return this.subproperty;
};
//override existing method
SubType.prototype.getSuperValue = function (){
  return false;
};
var instance = new SubType();
alert(instance.getSuperValue()); //false

另外一個需要注意的是,通過原型鏈實現繼承時,不能使用對象字面量建立原型方法,因為這樣會重寫原型鏈。如下面的代碼,在SubType繼承SuperType以後,使用對象字面量給原型添加方法,但這樣做,會重寫SubType原型,重寫後的SubType.prototype包含的是一個Object的執行個體,從而也切斷了與SuperType的關係。
複製代碼 代碼如下:
function SuperType(){
  this.property = true;
}
SuperType.prototype.getSuperValue = function(){
  return this.property;
};
function SubType(){
  this.subproperty = false;
}
//inherit from SuperType
SubType.prototype = new SuperType();
//try to add new methods - this nullifies the previous line
SubType.prototype = {
  getSubValue : function (){
    return this.subproperty;
  },
  someOtherMethod : function (){
    return false;
  }
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!

原型鏈的問題:和原型一樣,當使用參考型別值的時候,原型鏈就會出問題了。回顧一下之前的內容,包含一個參考型別值的原型屬性會被所有執行個體共用,這就是為什麼我們要把參考型別值在建構函式中定義,而不是在原型中定義。在通過原型鏈實現繼承時,原型實際上會變成另一個類型的執行個體,於是,原先的執行個體屬性也順利成章的變成現在的原型屬性了。
複製代碼 代碼如下:
function SuperType(){
  this.colors = [“red”, “blue”, “green”];
}
function SubType(){
}
//inherit from 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”

在SuperType建構函式中,我們定義了一個colors數組,每一個SuperType執行個體都會擁有它自己的這個colors數組。但是當SubType使用原型鏈繼承SuperType以後,SubType.prototype變成SuperType的一個執行個體,因此它擁有自己的colors屬性,也就是說SubType.prototype.colors屬性。所以,當建立SubType執行個體的時候,所有執行個體都共用這一屬性了。如上面的代碼所示。

第二個問題就是:在建立子類的執行個體時,不能向超類的建構函式中傳遞參數。實際上,應該說是沒有辦法在不影響所有對象執行個體的情況下,給超類的建構函式傳遞參數。由於這些問題,我們不會單獨使用原型鏈。

--------------------------------------------------------------------------------

借用建構函式(Contructor stealing):
為瞭解決上述問題,開發人員發明了一種叫做借用建構函式的技術。這種技術的思路就是:在子類型建構函式的內部調用超類型建構函式。(函數,只不過是在特定環境中執行代碼的對象?)我們可以通過使用apply()或call()方法在新建立的對象上執行建構函式。
複製代碼 代碼如下:
function SuperType(){
  this.colors = [“red”, “blue”, “green”];
}
function SubType(){
  //inherit from 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”

我們在SubType裡使用call()方法調用SuperType的建構函式,實際上就是在新的SubType對象上執行SuperType()函數中定義的所有對象初始化代碼。結果就是每個SubType執行個體都具有自己的colors屬性的副本。

傳遞參數:使用借用建構函式方法的一個很大的好處在於就是,我們可以從子類的建構函式傳遞參數到父類的建構函式中。
複製代碼 代碼如下:
function SuperType(name){
  this.name = name;
}
function SubType(){
  //inherit from SuperType passing in an argument
  SuperType.call(this, “Nicholas”);
  //instance property
  this.age = 29;
}
var instance = new SubType();
alert(instance.name); //”Nicholas”;
alert(instance.age); //29

新的SuperType建構函式新增了一個參數name,我們在call SuperType的同時,往SuperType傳遞參數"Nicholas"。為了不讓超類型的建構函式重寫子類型的屬性,可以在調用超類型建構函式後再定義子類的屬性。

借用建構函式的問題:方法都在建構函式中定義,無法複用。而且在超類型的原型中定義的方法,對子類型而言是不可見的。結果所有類型都只能使用建構函式模式。

--------------------------------------------------------------------------------

組合繼承:
結合原型鏈及借用建構函式各自的優點的一種繼承模式。使用原型鏈繼承原型屬性及方法,使用借用建構函式來繼承執行個體屬性。如下面例子,我們使用call()方法調用SuperType的建構函式(每個SubType執行個體都擁有自己的name和colors屬性,以及SubType的age屬性);然後再把SuperType執行個體賦值給SubType的原型,使其繼承SuperType的sayName()方法(每個執行個體都共用這個方法)。
複製代碼 代碼如下:
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name, age){
    //inherit properties
    SuperType.call(this, name);
    this.age = age;
}
//inherit methods
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

原型式繼承(Prototypal Inheritance):
複製代碼 代碼如下:
function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}
--------------------------------------------------------------------------------


寄生式繼承(Parasitic Inheritance):

缺點同建構函式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.