javascript實現的繼承的幾種常用方式

admin發表於2017-04-05

作為一門物件導向的語言,繼承自然是一大特徵之一。

但是javascript並不是一門像c#或者java一樣特別標準的物件導向的語言(當前來說)。

所以實現繼承方式也是多種多樣,下面就簡單分享一下常用的幾種方式,需要的朋友可以做一下參考。

一.原型鏈方式:

[JavaScript] 純文字檢視 複製程式碼
function SuperType(){
  this.property = 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();
console.log(instance.getSuperValue()); //true

實現的本質是重寫原型物件,代之以一個新型別的例項。

二.借用建構函式方式:

[JavaScript] 純文字檢視 複製程式碼
function SuperType(){
  this.colors = ["red", "blue", "green"];
}
function SubType(){
  //繼承了SuperType
  SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
console.log(instance2.colors); //"red,blue,green"

如果僅僅是借用建構函式,那麼也將無法避免建構函式模式存在的問題——方法都在建構函式中定義,因此函式複用就無從談起了。而且,在超型別的原型中定義的方法,對子型別而言也是不可見的,結果所有型別都只能使用建構函式模式。考慮到這些問題,借用建構函式的技術也是很少單獨使用的。

三.組合繼承方式:

[JavaScript] 純文字檢視 複製程式碼
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "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.prototype.sayAge = function(){
  console.log(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
console.log(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

在這個例子中,SuperType 建構函式定義了兩個屬性:name 和colors。SuperType 的原型定義了一個方法sayName()。SubType 建構函式在呼叫SuperType 建構函式時傳入了name 引數,緊接著又定義了它自己的屬性age。然後,將SuperType 的例項賦值給SubType 的原型,然後又在該新原型上定義了方法sayAge()。這樣一來,就可以讓兩個不同的SubType 例項既分別擁有自己屬性——包括colors 屬性,又可以使用相同的方法了。

組合繼承避免了原型鏈和借用建構函式的缺陷,融合了它們的優點,成為JavaScript 中最常用的繼承模式。而且,instanceof 和isPrototypeOf()也能夠用於識別基於組合繼承建立的物件。

四.原型式繼承:

[JavaScript] 純文字檢視 複製程式碼
function object(o){
  function F(){}
  F.prototype = o;
  return new F();
}

在object()函式內部,先建立了一個臨時性的建構函式,然後將傳入的物件作為這個建構函式的原型,最後返回了這個臨時型別的一個新例項。從本質上講,object()對傳入其中的物件執行了一次淺複製。來看下面的例子。

[JavaScript] 純文字檢視 複製程式碼
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"

克羅克福德主張的這種原型式繼承,要求你必須有一個物件可以作為另一個物件的基礎。如果有這麼一個物件的話,可以把它傳遞給object()函式,然後再根據具體需求對得到的物件加以修改即可。在這個例子中,可以作為另一個物件基礎的是person 物件,於是我們把它傳入到object()函式中,然後該函式就會返回一個新物件。這個新物件將person 作為原型,所以它的原型中就包含一個基本型別值屬性和一個引用型別值屬性。這意味著person.friends 不僅屬於person 所有,而且也會被anotherPerson以及yetAnotherPerson 共享。實際上,這就相當於又建立了person 物件的兩個副本。

ECMAScript 5 通過新增Object.create()方法規範化了原型式繼承。這個方法接收兩個引數:一個用作新物件原型的物件和(可選的)一個為新物件定義額外屬性的物件。在傳入一個引數的情況下,Object.create()與object()方法的行為相同。

[JavaScript] 純文字檢視 複製程式碼
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
console.log(person.friends); //"Shelby,Court,Van,Rob,Barbie"

Object.create()方法的第二個引數與Object.defineProperties()方法的第二個引數格式相同:每個屬性都是通過自己的描述符定義的。以這種方式指定的任何屬性都會覆蓋原型物件上的同名屬性。例如:

[JavaScript] 純文字檢視 複製程式碼
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
  
var anotherPerson = Object.create(person, {
  name: {
    value: "Greg"
  }
});
console.log(anotherPerson.name); //"Greg"

支援Object.create()方法的瀏覽器有IE9+、Firefox 4+、Safari 5+、Opera 12+和Chrome。

在沒有必要興師動眾地建立建構函式,而只想讓一個物件與另一個物件保持類似的情況下,原型式繼承是完全可以勝任的。不過別忘了,包含引用型別值的屬性始終都會共享相應的值,就像使用原型模式一樣。

五.寄生式繼承:

寄生式(parasitic)繼承是與原型式繼承緊密相關的一種思路,並且同樣也是由克羅克福德推而廣之的。寄生式繼承的思路與寄生建構函式和工廠模式類似,即建立一個僅用於封裝繼承過程的函式,該函式在內部以某種方式來增強物件,最後再像真地是它做了所有工作一樣返回物件。以下程式碼示範了寄生式繼承模式。

[JavaScript] 純文字檢視 複製程式碼
function createAnother(original){
  var clone = object(original); //通過呼叫函式建立一個新物件
  clone.sayHi = function(){ //以某種方式來增強這個物件
    console.log("hi");
  };
  return clone; //返回這個物件
}

在這個例子中,createAnother()函式接收了一個引數,也就是將要作為新物件基礎的物件。然後,把這個物件(original)傳遞給object()函式,將返回的結果賦值給clone。再為clone 物件新增一個新方法sayHi(),最後返回clone 物件。可以像下面這樣來使用createAnother()函式:

[JavaScript] 純文字檢視 複製程式碼
var person = {
  name: "Nicholas",
  friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"

這個例子中的程式碼基於person 返回了一個新物件——anotherPerson。

新物件不僅具有person的所有屬性和方法,而且還有自己的sayHi()方法。

在主要考慮物件而不是自定義型別和建構函式的情況下,寄生式繼承也是一種有用的模式。

前面示範繼承模式時使用的object()函式不是必需的。

任何能夠返回新物件的函式都適用於此模式。

使用寄生式繼承來為物件新增函式,會由於不能做到函式複用而降低效率,這一點與建構函式模式類似。

相關文章