javascript學習筆記(19)--原型繼承

引入

在傳統的基於Class的語言如Java、C++中,繼承的本質是擴展一個已有的Class,並生成新的Subclass
由於這類語言嚴格區分類和實例,繼承實際上是類型的擴展
但是,JavaScript由於採用原型繼承,我們無法直接擴展一個 Class,因爲根本不存在Class這種類型, 那麼該怎麼辦呢?

我們先回顧Student構造函數

function Student(stdt) { 
this.name = stdt.name; 
Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); }
}

之後我們創建實例最好就傳入對象,這樣我們自己也不用考慮順序,也好操作

現在,我們要基於Student擴展出PrimaryStudent,可以先定義出PrimaryStudent

 function PrimaryStudent(stdt) { 
 Student.call(this, stdt);
  this.grade = stdt.grade || 1; }

call的用法直接也講過啦,這裏在簡單提一下,
f.call(this,x,y,z)
this是我們要綁定的對象,x,y,z是我們f的參數,這裏我們參數只有一個對象,於是就只傳入stdt

但是,調用了Student構造函數不等於繼承了Student
在這裏插入圖片描述額這個是函數的原型鏈
PrimaryStudent創建的對象的原型鏈
在這裏插入圖片描述
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null
不過之後我們還是主要關注實例的原型鏈,因爲最終我們需要的其實是一個個實例
在js種,創建實例其實是通過函數生成的,一個個函數,我們既可以當他們是一個個具體的函數實例,比如說Math.abs,它可以計算,這個時候就是一個具體的實例了,也可以當成是一個類,比如說這裏的Student,我們不會去調用它,但我們會利用它擴展生成一類實例

因爲我們是通過Student擴展出的PrimaryStudent,所以我們希望原型鏈爲
xiaoming ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null
這樣,原型鏈對了,繼承關係就對了,新的基於Primary Student創建的對象不但能調用PrimaryStudent.prototype定義的方法,也可以調用Student.prototype定義的方法 ,這纔是我們想要的繼承
這樣的話其實也可以解決我們的代碼負擔,免得還要把方法再寫一遍

解決方法

第一種
因爲我們PrimaryStudent.prototype指向的是Object.prototype我們修改一下
在這裏插入圖片描述在這裏插入圖片描述我們只需要把PrimaryStudent.prototype的上一級改成Student.prototype就好了呀
在這裏插入圖片描述
而且也不影響函數的原型鏈

第二種(也是老的方法)
我們可以藉助一箇中間對象來實現正確的原型鏈,這個中間對象的原型要指向Student.prototype,爲了實現這一點,中間對象可以用一個空函數F來實現
首先看看如果一個函數修改了prototype會有什麼變化
在這裏插入圖片描述在這裏插入圖片描述(最下面灰色的是函數的原型鏈,跟我們這裏沒關係,而且我看了,確實也不影響)
修改了prototype之後的F果然是大變活人呀,真的一摸一樣
在這裏插入圖片描述剛纔第一種我是這樣修改的,沒啥問題

function F(){};
F.prototype = Student.prototype;

但如果定義在了函數內部,記着要調用一下自己喲
在這裏插入圖片描述好啦,下面開始我們繼承啦

 function PrimaryStudent(stdt) { 
 Student.call(this, stdt); 
 this.grade = stdt.grade || 1;
  }
function F() { };//因爲怕自己忘記調用了,還是我們手動修改
F.prototype = Student.prototype;
 PrimaryStudent.prototype = new F(); 

在這裏插入圖片描述這樣繼承不知道能不能理解
之前我們的new F()是用來創建實例,這裏我們把new F()創建的實例賦值給了PrimaryStudent,其實也不要把new F()想狹隘了,new F()就是給你創造一個空的對象,然後裏面給你加個繼承關係,剛好適合我們這裏的prototype
(這是原來的PrimartStudent)
這裏獲得好了繼承關係後,但是我們沒有構造函數了,因爲new的作用機理,給的是空對象和繼承關係,把你原來的prototype裏的內容清空了,所以我們自己需要把構造函數補上

PrimaryStudent.prototype.constructor = PrimaryStudent;

在這裏插入圖片描述跟修改之前看起來差不多,但其實繼承關係變啦
在這裏插入圖片描述第三種方法
只是把第二種方法變成個函數
如果把繼承這個動作用一個inherits()函數封裝起來,還可以隱藏F的定義,並簡化代碼:

 function inherits(Child, Parent) { 
 var F = function () {}; 
 F.prototype = Parent.prototype; 
 Child.prototype = new F(); 
 Child.prototype.constructor = Child; 
 }

這個inherits()函數可以複用

小結

JavaScript的原型繼承實現方式就是

  1. 定義新的構造函數,並在內部用call()調用希望“繼承”的構造函數,並綁定this;
  2. 藉助中間函數F實現原型鏈繼承,最好通過封裝的inherits函數完成;
  3. 繼續在新的構造函數的原型上定義新方法
  4. 定義prototype裏的方法和屬性是可以在函數內部,但是如果是直接修改prototype爲另一個函數的,最好是在外面,免得忘記執行了(方法和屬性因爲實例化的時候new已經主動調用了,所以不影響)

小小疑惑

後來我又發現,我們這一步沒什麼效果

PrimaryStudent.prototype.constructor = PrimaryStudent;

在這裏插入圖片描述在這裏插入圖片描述這個時候相當於我們prototype下面就沒有構造函數

在這裏插入圖片描述其實兩個真的差不多,難道constructor真的是個擺設嗎

我們來測試一下
在這裏插入圖片描述把整個prototype改空後,運行不了了

只修改了constructor
在這裏插入圖片描述正常情況
在這裏插入圖片描述兩種好像真的差不多,額所以這裏…我個人覺得吧可能constructor只是爲了方便我們…,沒什麼真實作用

在這裏插入圖片描述
在這裏插入圖片描述
而且還有一點,就是prototype下定義的變量,函數,如果有新創建對象,一定要注意,所有的值會重新被初始化,因爲我們new Student調用了Student,他又會執行一遍賦值的活動,所以…這一點也要注意一下,(因爲所有實例都是訪問它,所以大家跟着一起變)
但是實例變量就沒這個問題了,可以認爲是無法修改的,因爲你在函數內部你的this綁定的是window(undefined)你怎麼也訪問不到,所以可以實例變量和方法是無法被改變的,除非你修改實例對象自己的,但還是跟構建函數無關

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章