本文承接上一篇JavaScript物件導向編程(1) 基礎。
上篇說過,JavaScript沒有類的概念,需要通過函數來實作類別的定義。先通過一個例子說明:
複製代碼 代碼如下:function myClass()
{
var id = 1;
var name = "johnson";
//properties
this.ID = id;
this.Name = name;
//method
this.showMessage = function()
{
alert("ID: " + this.ID + ", Name: " + this.Name);
}
}
var obj1 = new myClass();
var obj2 = new myClass();
function的定義實際上相當於類的建構函式,最後兩句是建立這個類的執行個體。先分析第一句:var obj1 = new myClass(); 當用new建立類的執行個體時,解譯器首先會建立一個空的對象。然後運行這個myClass函數,並將this指標指向這個類的執行個體。當碰到this.ID = id;和this.Name = name;及this.showMessage = function(){...}時,便會建立這兩個屬性,和這個方法,並把變數id,name的值一級函數的定義賦給這兩個屬性及這個函數對象(shwoMessage)。這個過程相當於初始化這個對象,類似於C# 中的建構函式。最後new返回這個對象。再看第二句:var obj2 = new myClass(); 執行過程與上一句代碼相同,即建立一個Null 物件,然後執行myClass這個函數,定義兩個屬性和一個方法。
從上面的分析中可以看到,上面這種實作類別的方式,即在函數的定義中定義類的屬性方法。存在著弊端。如果需要建立兩個或更多這個類的執行個體時,上文是兩個,這些屬性會被重複的建立多次。
那麼如何避免這種情況呢?上一篇中也曾提到過用prototype。prototype和它的名字一樣是一個原型,每一個function都有一個子物件prototype,它其實表示這個function對象的成員的集合,由於這裡我們使用function實作類別的,所以可以說prototype其實就是便是類的成員的集合。prototype定義的屬性和方法執行在函數的構造體執行之前,所以當new一個對象之前,其實prototype的成員已經執行過了。先看一個例子: 複製代碼 代碼如下:function myClass()
{
//建構函式
}
myClass.prototype =
{
ID: 1,
Name: "johnson",
showMessage: function()
{
alert("ID: " + this.ID + ", Name: " + this.Name);
}
}
var obj1 = new myClass();
var obj2 = new myClass();
類的結構還是和前面的例子相同,只不過這裡是利用了prototype來實現。還是先看最後兩句,前面說過,prototype是執行在函數構造體之前,即執行到var obj1 = new myClass();之前,這個類已經有了ID,Name屬性和showMessage方法。執行者一句時執行過程如下,注意和前一個例子比較:首先還是建立一個空的對象,並把this指標指向這個對象。然後將函數的prototype對象的所有成員都賦給這個對象(注意沒有再建立這些成員)。然後執行函數體。最後new返回這個對象。執行下一句時:同樣執行此過程,不會重複建立這些成員。
上面的代碼還只是一個例子,在實際的項目中,可能出現的是類中有大量的成員,同時可能需要建立大量的執行個體。這是prototype就會顯示其優越性了。另外上面的代碼中使用了大括弧文法定義了prototype的成員,這樣看起來代碼更清晰。這是一種比較推薦的類的設計模式。當然在眾多的項目中,可能還會發現更好的模式,我們也希望能有更最佳化的JavaScript的編程模式不斷推陳出新,也希望隨著時間的推移,各主流瀏覽器也對JavaScript的解析都標準,統一。
上面說過prototype定義的成員是發生在構造體之前,可以證明一下,在上面的例子中,構造體是空的,在建構函式中加入一句alert(this.Name);,當執行到var obj1 = new myClass();時,會看到彈出對話方塊,顯示正確的屬性值。
寫了這段文字之後承蒙多為兄弟的點評,收穫匪淺。對上面的例子進一步討論,如下代碼: 複製代碼 代碼如下:function subClass(){ }
subClass.prototype =
{
Name: "sub"
}
function myClass()
{
//建構函式
}
myClass.prototype =
{
ID: 1,
Name: "johnson",
SubObj: new subClass(),
showMessage: function()
{
alert("ID: " + this.ID + ", Name: " + this.Name + "SubObj.Name:" + this.SubObj.Name);
}
}
var obj1 = new myClass();
obj1.SubObj.Name = "XXX";
obj1.showMessage();
var obj2 = new myClass();
obj2.showMessage();
這裡在myClass中定義了一個參考型別,其類型是我們自訂的一個subClass類,這個子類中有一個Name屬性。由於prototype對象是共用的,按照我們上面的分析:在執行var obj1 = new myClass();時,會把myClass的prototype中的成員複製給這個obj1執行個體。但這裡SubObj是一個參考型別,在執行到var obj2 = new myClass();時,prototype中的ID,Name成員會複製到obj2中,但SubObj這個屬性不會複製過去,而是引用了prototype中的SubObj,所以因為上一句修改了obj1.Subobj.Name的值,所以在用new產生obj2執行個體時,引用到了修改後的值。
所以借用prototype定義類時,依然需要將屬性定義在構造體中,而將方法定義在該構造體的原型上。如下: 複製代碼 代碼如下:function myClass(id, name)
{
this.ID = id;
this.Name = name;
}
myClass.prototype =
{
showMessage: function()
{
alert("ID: " + this.ID + ", Name: " + this.Name);
},
showMessage2: function()
{
alert("Method2");
}
}
var obj1 = new myClass(1, "johnson");
obj1.showMessage();
obj1.Name="John";
obj1.showMessage();
var obj2 = new myClass(2, "Amanda");
obj2.showMessage();
關於私人成員,共有成員以及靜態成員,類的繼承,抽象類別,虛方法,類的反射等實現方法,以後還會堅持寫下去。不過我覺得需要說一下的是,我打算寫的是JavaScript物件導向的基礎實現,如果需要深入的學習建議參考李戰老哥的“甘露模型”。