淺析Javascript原型繼承(轉)

引自: 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();  
  
/* 
方法能夠隨時添加,添加完以後就能夠調用 
*/  

 

問題三:

Hoozit.prototype.toString = function () {  
    return "hoozit--toString:" + this.id;  
};  
alert(h);  
delete Hoozit.prototype.toString;  
alert(h);  
  
/* 
這個問題和問題一有些重複,這裏更清楚的看出,刪除掉本身原型上的方法以後,就會找父原型中的方法 
*/  

 

問題四:

h.question.call(g);  
alert(h.toString.call(g));  
  
h.question.apply(g);  
alert(h.toString.apply(g));  
  
/* 
能夠利用apply和call方法把要調用的方法綁定到其它實例。經過結果能夠看出上面那種方法調用輸出的id是g對象的,而不是h對象的 
*/  

 

問題五:

alert(h.privateMethod());  
alert(g.privateMethod2());  
  
/* 
上面的任意一個調用都會報錯,也就是說經過顯示命名函數或者匿名函數可是不加this的聲明方式定義在函數以內的函數,是不能被外界訪問的。這裏必定注意第二種private方法聲明,省略了this外面就訪問不到了! 
*/  
  
/* 
命名函數:(函數名字爲func1) 
function func1(){} 
 
匿名函數:(注意這裏,func1不是函數的名字,僅僅是個別名而已,能夠經過func()來調用這個匿名函數) 
func1=function(){} 
*/  
相關文章
相關標籤/搜索