引自: http://blog.csdn.net/kittyjie/article/details/4380918 原做者解釋的淺顯易懂,很是不錯的JavaScript prototype總結javascript
JS沒有提供所謂的類繼承,聽說在2.0中要加入這種繼承方式,可是要全部瀏覽器都實現2.0的特性那確定又得N多年。昨天看了crockford的一個視頻,裏面講解了一下JS的繼承方式,按照PPT裏面說的,一共分了三類:Prototypal,pseudoclassical,Parasitic Inheritance。java
下面主要介紹一下原型繼承:When a function object is created, it is given a prototype member which is an object containing a constructor member which is a reference to the function object. 編程
這裏先區別一下什麼是prototype屬性,和constructor屬性。也就是要區別什麼是構造器,函數,對象實例。瀏覽器
其實在JS中構造器就是函數,函數就是構造器,對象實例就是經過var obj=new 函數();這種形式新建出來的實例。區別這些,在說prototype和constructor。從上面的英文中能夠看出,prototype是個對象,裏面定義了一個constructor,那麼咱們能夠推論出,constructor是對象實例的屬性!而不是函數(構造器)的屬性。反過來,prototype是函數(構造器)的屬性,而不是實例的屬性!app
//在下面這個代碼示例中,MyObj是函數(構造器),obj是實例 function MyObj(id){ this.id=id; } var obj=new MyObj(1); alert(MyObj.constructor) //本地代碼 alert(obj.constructor) //MyObj.toString() alert(MyObj.prototype) //[object Object] alert(obj.prototype) //undefined
咱們能夠看出MyObj是不具備JS意義下的constructor屬性的,爲何這麼說呢。alert(MyObj.constructor)這行代碼仍是有東西的:框架
這是由於MyObj是個函數,因此他的構造器就是本地的Function對象,也就是說MyObj是由Function構造出來的。可是這個對咱們意義不大,由於這已經再也不JS層面上了。因此這裏能夠認爲MyObj不具備JS意義下的constrcutor屬性。函數
alert(obj.prototype)經過這行咱們能夠看出,obj實例是不具備原型屬性的。OK,如今區別清楚了這些,能夠看原型繼承了。若是不區別清楚這個,恐怕下面會看的很暈。this
function Gizmo(id) { this.id = id; } Gizmo.prototype.toString = function () { return "gizmo " + this.id; }; function Hoozit(id) { this.id = id; } Hoozit.prototype = new Gizmo(); Hoozit.prototype.test = function (id) { return this.id === id; };
注意這行:Hoozit.prototype = new Gizmo();這行就是原型繼承的核心代碼。spa
還有要注意的是隻有在new Gizmo()以後,才能添加test等其它方法,這個順序不能倒過來!若是你先添加了test等方法,而後在new Gizmo(),那麼原來添加的那些方法都將找不到了。具體緣由,下面分析完了,也就清楚了。.net
Hoozit.prototype.test = function (id) { return this.id === id; }; Hoozit.prototype = new Gizmo(2); var h=new Hoozit(); alert(h.test(3)); //這裏會報錯!!
仔細看一下上面的圖,這個就是原型繼承的圖示。左下角new Hoozit(stirng)表明的是新建的一個對象。爲了如下表述方便,咱們管他叫objH1。最右邊的灰色的箭頭就是原型繼承鏈。
根據文章開頭的那段英文,咱們知道每一個函數都有一個原型,這個原型是個對象,而且對象裏面包含一個constructor屬性。其中Object,Gizmo,Hoozit就是函數,能夠看出裏面都有一個prototype項,而這個prototype又指向一個對象,這個對象裏面又有一個constructor屬性,constructor又指回自身。
alert(Gizmo.prototype.constructo===Gizmo) //true
可是這裏有一個意外,咱們發現Hoozit原型對象裏面沒有constructor屬性,而這個函數的右邊卻有一個空的對象,裏面包含了一個constructor屬性?爲何呢?
這個問題會發生在原型繼承過程當中。主要就是由於Hoozit.prototype = new Gizmo();這句話引發的。這句話的意思就是新建了一個Gizmo對象而且賦給Hoozit的原型!那麼,那麼,仔細想一想,是否是Hoozit原有的原型對象就被斷開了呢??沒錯,就是這樣。因此那個有constructor屬性的空對象再也訪問不到了!
那如今又有一個疑問了,經過Hoozit.prototype = new Gizmo();這行代碼以後,Hoozit.prototype.constructor指向哪裏了呢?很簡單,知道(new Gizmo()).constructor指向哪裏嗎?經過上面的圖,能夠清晰的看出來指向的是Gizmo函數。因此咱們判定,如今的Hoozit.prototype.constructor也指向了那裏!
alert(Hoozit.prototype.constructor===Gizmo); //true
上面這行代碼驗證了咱們的猜想!OK,上面講完了函數(構造器一邊),而後咱們再來講實例對象這邊:每一個實例對象都有一個constructor屬性,而且指向構造器(函數)。並且每一個new出來的實例都是某個原型constructor的實例:
var objG1=new Gizmo() alert(objG1 instanceof Gizmo.prototype.constructor) //true alert(Gizmo.prototype.constructor===objG1.constructor); //true
上面爲何不拿objH1舉例呢,由於他的constructor已經不是他本身的了,而是Gizmo對象的,那麼咱們驗證一下:
alert(objH1.constructor===objG1.constructor) //true alert(objH1 instanceof Gizmo.prototype.constructor) //true
看到了嗎?其實這個問題也不算什麼大問題,若是你要不使用instanceof檢查父對象或者使用constructor進行原型回溯的話,這個問題能夠不解決了。若是想解決這個問題怎麼辦呢?在Prototype框架的Class.create方法裏面給出了一種方法,具體能夠參考:http://blog.csdn.net/kittyjie/archive/2009/07/13/4345568.aspx
下面我簡單說一下兩種方法:
//方法一 //Prototype框架採用了此種方法 Hoozit.prototype = new Gizmo(2); Hoozit.prototype.constructor = Hoozit; //方法二 function Hoozit(id) { this.id = id; this.constructor=Hoozit; } //具體兩種方法有什麼區別,請參考《JAVASCRIPT語言精髓與編程實踐》158~159頁。
有興趣的能夠結合上面的圖,想一想這兩種方法的Hoozit.prototype.constructor應該放在圖的什麼位置?想不明白的能夠和我在交流。
下面看一下《JAVASCRIPT語言精髓和編程實踐》書上的一張圖(156~157頁):
我的認爲這張圖下面的那個constructor屬性的指向是否是有問題?書上面表達的意思確定沒有問題,但這張圖畫的很困惑。和我上面的解釋不太同樣?先不說這個了,你們本身研究研究吧。
下面咱們再來講一下我給出的原型繼承圖中的右邊灰色的箭頭部分。從這部分可以看出繼承的關係。全部未通過變更的函數(構造器)的原型,他們都會繼承自Object對象。
alert(Gizmo.prototype instanceof Object.prototype.constructor); //true
這樣原型的繼承關係就鏈接起來了。其實說白了,就是一個函數的prototype鏈向另外一個函數實例,而後不斷的這樣進行下去,最上面的函數連接Object至對象實例,OK,全部函數就都鏈接起來了。
PS:
「還有要注意的是隻有在new Gizmo()以後,才能添加test等其它方法,這個順序不能倒過來!「 這個問題是否是清楚了呢?
================================================
下面看幾個例子,說明幾個問題:
function Gizmo(id) { this.id = id; this.ask=function(){ alert("gizmo--ask:"+this.id); } function privateMethod(){ return "gizmo--privateMethod"; } privateMethod2=function(){ return "gizmo--privateMethod2"; } } Gizmo.prototype.toString = function () { return "gizmo--toString:" + this.id; }; Gizmo.prototype.id="gizmo3"; function Hoozit(id) { this.id = id; } Hoozit.prototype = new Gizmo("gizmo1"); var g=new Gizmo("gizmo2"); var h=new Hoozit("hoozit");
問題一:
h.ask=function(){ alert("h.ask"); } h.ask(); delete h.ask; //"h.ask" h.ask(); //"gizmo--ask:hoozit" delete h.id h.ask(); //"gizmo--ask:gizmo1" delete Hoozit.prototype.id h.ask(); //"gizmo--ask:gizmo3" /* 這裏要說明的問題是:對象是如何找到屬性和方法的? 第一步:先在實例對象上找ask方法,找到了,調用。第一個ask說明了這個問題 第二步:若是實例上沒有ask方法,在本身的原型對象裏面找ask方法,找到調用(沒有給出這樣的示例) 第三步:若是本身的原型中沒有,回溯原型鏈,在父原型鏈中找ask方法,找到調用,第二個ask說明了這個問題,這裏會一直遞歸找到Object對象,若是還沒找到,那就會報錯了 */ /* 再來看屬性查找: 首先找實例對象上的屬性,因此第二個ask輸出"gizmo--ask:hoozit",即id="hoozit" 而後找本身原型中的屬性,刪除掉h.id以後,找到原型上的屬性,因此id="gizmo1" 接着遞歸原型鏈,找父對象原型中的屬性,一直找到Object對象,因此刪除掉Hoozit.prototype.id以後,id="gizmo3" */
問題二:
Gizmo.prototype.question = function () { alert("gizmo--question:" + this.id); }; h.question(); /* 方法能夠隨時添加,添加完以後就能夠調用 */
問題三:
問題四:
問題五: