漫談JS 的繼承方式


一.原型鏈
原型鏈的基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的指針。若是:咱們讓原型對象A等於另外一個類型B的實例,那麼原型對象A就會有一個指針指向B的原型對象,相應的B的原型對象中保存着指向其構造函數的指針。假如B的原型對象又是另外一個類型的實例,那麼上述的關係依舊成立,如此層層遞進,就構成了實例與原型的鏈條數組

一、默認的原型
前面的例子中展現的原型鏈少了一環,全部引用類型默認都繼承了Object,而這個繼承也是經過原型鏈實現的。所以默認的原型都包含一個內部指針,指向Object.prototype,這也正是全部自定義類型會繼承toString()、ValueOf()等默認方法的根本緣由。換句話說Object.prototype就是原型鏈的末端。
二、肯定原型和實例的關係
經過兩種方式能夠肯定原型和實例之間的關係,第一種是使用instanceOf操做符,第二種是使用isPrototypeOf()方法。 
實例 instanceOf 原型鏈 中出現過的構造函數,都會返回true
JavaScript中isPrototypeOf函數方法是返回一個布爾值,指出對象是否存在於另外一個對象的原型鏈中。使用方法:app

object1.isPrototypeOf(object2)函數

三、謹慎地定義方法
子類型有時候須要覆蓋超類型中的某個方法,或者須要添加超類型中不存在的莫個方法,注意:給原型添加方法的代碼必定要放在替換原型的語句以後。
當經過Child的實例調用getParentValue()時,調用的是這個從新定義過的方法,可是經過Parent的實例調用getParentValue()時,調用的仍是原來的方法。 
格外須要注意的是:必需要在Parent的實例替換原型以後,再定義這兩個方法。
還有一點須要特別注意的是:經過原型鏈實現繼承時,不能使用對象字面量建立原型方法,由於這樣作會重寫原型鏈。this

四、原型鏈的問題
原型鏈很強大,能夠利用它來實現繼承,可是也有一些問題,主要的問題仍是包含引用類型值的原型屬性會被全部實例共享。所以咱們在構造函數中定義實例屬性。可是在經過原型來實現繼承時,原型對象其實變成了另外一個類型的實例。因而原先定義在構造函數中的實例屬性變成了原型屬性了。spa

2.借用構造函數
爲了解決原型中包含引用類型值帶來的一些問題,引入了借用構造函數的技術。這種技術的基礎思想是:在子類型構造函數的內部調用超類型構造函數。
Parent.call(this)在新建立的Child實例的環境下調用了Parent構造函數。在新建立的Child實例環境下調用Parent構造函數。這樣,就在新的Child對象上,此處的kid1和kid2對象上執行Parent()函數中定義的對象初始化代碼。這樣,每一個Child實例就都會具備本身的friends屬性的副本了。
借用構造函數的方式能夠在子類型的構造函數中向超類型構造函數傳遞參數
爲了確保子類型的熟悉不會被父類的構造函數重寫,能夠在調用父類構造函數以後,再添加子類型的屬性。 
構造函數的問題:
構造函數模式的問題,在於方法都在構造函數中定義,函數複用無從談起,所以,借用構造函數的模式也不多單獨使用。prototype

3.組合繼承
組合繼承指的是將原型鏈和借用構造函數的技術組合在一塊,從而發揮兩者之長。即:使用原型鏈實現對原型屬性和方法的繼承,而經過借用構造函數來實現對實例屬性的繼承。 
Person構造函數定義了兩個屬性:name和friends。Person的原型定義了一個方法sayName()。Child構造函數在調用Parent構造函數時,傳入了name參數,緊接着又定義了本身的屬性age。而後將Person的實例賦值給Child的原型,而後又在該原型上定義了方法sayAge().這樣,兩個不一樣的Child實例既分別擁有本身的屬性,包括引用類型的屬性,又可使用相同的方法了。 
組合繼承避免了原型鏈和構造函數的缺陷,融合了他們的有點,成爲JavaScript中最經常使用的繼承模式。並且,instanceOf和isPropertyOf()也可以識別基於組合繼承建立的對象。指針

4.對象冒充
對象冒充包括三種:臨時屬性方式、call()及apply()方式
   1.臨時屬性方式:      code

複製代碼
1 function A(x){
2   this.x=x;
      this.say = function(){
          alert('My name is '+this.name);
      }
3 }  
4  function B(x,y){
5   this.tmpObj=A;
6   this.tmpObj(x);
7   delete this.tmpObj;
8   this.id = id;
      this.showId = function(){
           alert('Good morning,Sir,My work number is '+this.id);
      }
9 }
複製代碼

 


var simon = new B('Simon',9527);
simon.say();
simon.showId();
/**/第五、六、7行:建立臨時屬性tmpObj引用構造函數A,而後在B內部執行,執行完後刪除。當在B內部執行了 this.x=x後(這裏的this是B的對象),B固然就擁有了x屬性,固然B的x屬性和A的x屬性二者是獨立,因此並不能算嚴格的繼承。第五、六、7行有更簡單的實現,就是經過call(apply)方法:A.call(this,x);對象

 2.call()/apply()方式:實質上是改變了this指針的指向、blog

複製代碼
 1 function Person(name){
 2      this.name = name;
 3      this.say = function(){
 4           alert('My name is '+this.name);
 5      }
 6 }
 7 function F2E(name,id){
 8      Person.call(this,name); //apply()方式改爲Person.apply(this,[name]);
 9      this.id = id;
10      this.showId = function(){
11           alert('Good morning,Sir,My work number is '+this.id);
12      }
13 }
14 var simon = new F2E('Simon',9527);
15 simon.say();
16 simon.showId();
複製代碼

 


  /**/call和apply均可以實現繼承,惟一的一點參數不一樣,func.call(func1,var1,var2,var3)對應的apply寫法爲:func.apply(func1,[var1,var2,var3])。區別在於 call 的第二個參數能夠是任意類型,而apply的第二個參數必須是數組,也能夠是arguments。

 經過對象冒充的方式,沒法繼承經過prototype方式定義的變量和方法:

5.原型式繼承
基本想法:藉助原型能夠基於已有的對象建立新對象,同時還沒必要須所以建立自定義的類型。
原型式繼承的思想可用如下函數來講明:

複製代碼
 1 function object(o) {
 2 function F(){}
 3 F.prototype = o;
 4 return new F();
 5 }
 6 例子:
 7 var person = {
 8 name:"EvanChen",
 9 friends:["Shelby","Court","Van"];
10 };
11 var anotherPerson = object(person);
12 anotherPerson.name = "Greg";
13 anotherPerson.friends.push("Rob");
14 var yetAnotherPerson = object(person);
15 yetAnotherPerson.name = "Linda";
16 yetAnotherPerson.friends.push("Barbie");
17 console.log(person.friends);//"Shelby","Court","Van","Rob","Barbie"
複製代碼

 


ECMAScript5經過新增Object.create()方法規範化了原型式繼承,這個方法接收兩個參數:一個用做新對象原型的對象和一個做爲新對象定義額外屬性的對象。

複製代碼
var person = {
name:"EvanChen",
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"
複製代碼

 

6.寄生式繼承
基本思想:建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後再像真正是它作了全部工做同樣返回對象。
例子:

複製代碼
 1 function createAnother(original) {
 2 var clone = object(original);
 3 clone.sayHi = function () {
 4 alert("hi");
 5 };
 6 return clone;
 7 }
 8 var person = {
 9 name:"EvanChen",
10 friends:["Shelby","Court","Van"];
11 };
12 var anotherPerson = createAnother(person);
13 anotherPerson.sayHi();///"hi"
複製代碼

 

7.寄生組合式繼承
基本思想:經過借用函數來繼承屬性,經過原型鏈的混成形式來繼承方法
其基本模型以下所示:

複製代碼
function inheritProperty(subType, superType) {
var prototype = object(superType.prototype);//建立對象
prototype.constructor = subType;//加強對象
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;
}
inheritProperty(SubType,SuperType);
SubType.prototype.sayAge = function() {
alert(this.age);
}
複製代碼
相關文章
相關標籤/搜索