JS沒有提供所謂的類繼承,據說在2.0中要加入這種繼承方式,但是要所有瀏覽器都實現2.0的特性那肯定又得N多年。昨天看了crockford 的一個視頻,裡面講解了一下JS的繼承方式,按照PPT裡面說的,一共分了三類:Prototypal,pseudoclassical,Parasitic Inheritance。
下面主要介紹一下原型繼承: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是函數(構造器)的屬性,而不是執行個體的屬性! 複製代碼 代碼如下://在下面這個程式碼範例中,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,現在區別清楚了這些,可以看原型繼承了。如果不區別清楚這個,恐怕下面會看的很暈。
複製代碼 代碼如下: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();這行就是原型繼承的核心代碼。
還有要注意的是只有在new Gizmo()之後,才能添加test等其它方法,這個順序不能倒過來!如果你先添加了test等方法,然後在new Gizmo(),那麼原來添加的那些方法都將找不到了。具體原因,下面分析完了,也就清楚了。 複製代碼 代碼如下: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屬性的Null 物件再也訪問不到了!
那現在又有一個疑問了,通過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
下面我簡單說一下兩種方法: