JavaScript的各種書籍說的可能會有點凌亂,今天看一下ECMAScript對定義類或對象的相關文檔。
面嚮對象語言不僅能夠使用預定義對象還能夠創建自己專用的類和對象。
原始方式
var oCar = new Object; oCar.color = "blue"; oCar.doors = 4; oCar.mpg = 25; oCar.showColor = function(){ alert(this.color); }
對象的屬性可以在對象創建後動態定義。上例中,創建對象oCar,然後給它設置幾個屬性。最後一個屬性實際上是指向函數的指針,意味着該屬性是個方法。
缺點:需要創建多個car的實例時麻煩。用工廠方式解決
工廠方式
開發者創造了能創建並返回特定類型的對象的工廠函數(factory function)。
function createCar(){ var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function(){ alert(this.color); }; return oTempCar; } var oCar1 = createCar(); var oCar2 = createCar();
上例中createCar()函數封裝了創建car對象的操作。調用此函數,將創建新對象,並賦予它所有必要的屬性,複製出一個car對象。這樣,就很容易地創建car對象的兩個版本(類的兩個實例)oCar1和oCar2。
帶參數傳遞的:
function createCar(sColor,iDoors,iMpg){ var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = function(){ alert(this.color); }; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); //輸出 "red" oCar2.showColor(); //輸出 "blue"
oCar1和oCar2兩個對象具有相同的屬性,卻有不同的屬性值。
在工廠函數外定義對象的方法
function showColor() { alert(this.color); } function createCar(sColor,iDoors,iMpg) { var oTempCar = new Object; oTempCar.color = sColor; oTempCar.doors = iDoors; oTempCar.mpg = iMpg; oTempCar.showColor = showColor; return oTempCar; } var oCar1 = createCar("red",4,23); var oCar2 = createCar("blue",3,25); oCar1.showColor(); //輸出 "red" oCar2.showColor(); //輸出 "blue"
在函數 createCar() 之前定義了函數 showColor()。在 createCar() 內部,賦予對象一個指向已經存在的 showColor() 函數的指針。從功能上講,這樣解決了重複創建函數對象的問題;但是從語義上講,該函數不太像是對象的方法。
構造函數方式
創建構造函數就像創建工廠函數一樣容易。第一步選擇類名,即構造函數的名字。根據慣例,這個名字的首字母大寫,以使他與首字母通常是小寫的變量名分開。除了這點不同,構造函數看起來很像工廠函數。
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.showColor = function() { alert(this.color); }; } var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25);
與工廠方式的差別。首先在構造函數內沒有創建對象,而是使用this關鍵字。使用new運算符構造函數時,在執行第一行代碼前創建一個對象,只有this才能訪問該對象。然後可以直接賦予this屬性,默認情況下是構造函數的返回值(不必明確使用return運算符)。
缺點:就像工廠函數,構造函數會重複生成函數,爲每個對象都創建獨立的函數版本。不過,與工廠函數相似,也可以用外部函數重寫構造函數,同樣地,這麼做語義上無任何意義。這正是下面要講的原型方式的優勢所在。
原型方式
該方式利用了對象的prototype屬性,可以把它看成創建新對象所以來的原型。
這裏,首先用空構造函數來設置類名。然後所有的屬性和方法都被直接賦予prototype屬性。
function Car() { } Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car();
這段代碼中,首先定義構造函數(Car),其中無任何代碼。接下來的幾行代碼,通過給Car的prototype屬性去定義Car對象的屬性。調用new Car()時,原型的所有屬性都被立即賦予要創建的對象,意味着所有Car實例存放的都是指向showColor()函數的指針。從語義上講,所有屬性看起來都屬於一個對象,因此解決了前面兩種方式存在的問題。
缺點:首先,這個構造函數沒有參數。使用原型方式,不能通過給構造函數傳遞參數來初始化屬性的值,因而Car1和Car2的屬性值相同。這意味着必須在對象創建後才能改變屬性的默認值。當屬性指向的不是函數時比如下例中的drivers屬性,此時該屬性指向的對象是對象實例共享的,但是被共享的屬性一般很少。
function Car() { } Car.prototype.color = "blue"; Car.prototype.doors = 4; Car.prototype.mpg = 25; Car.prototype.drivers = new Array("Mike","John"); Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car(); var oCar2 = new Car(); oCar1.drivers.push("Bill"); alert(oCar1.drivers); //輸出 "Mike,John,Bill" alert(oCar2.drivers); //輸出 "Mike,John,Bill"
由於 drivers 是引用值,Car 的兩個實例都指向同一個數組。這意味着給 oCar1.drivers 添加值 "Bill",在 oCar2.drivers 中也能看到。輸出這兩個指針中的任何一個,結果都是顯示字符串 "Mike,John,Bill"。說了半天,就是通過prototype擴展對象時,擴展的方法或屬性將適用於對象的所有實例。
混合的構造函數/原型方式
用構造函數定義對象的所有非函數屬性,用原型方式定義對象的函數屬性(方法)。結果是,所有函數都只創建一次,而每個對象都具有自己的對象屬性實例。
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); } Car.prototype.showColor = function() { alert(this.color); }; var oCar1 = new Car("red",4,23); var oCar2 = new Car("blue",3,25); oCar1.drivers.push("Bill"); alert(oCar1.drivers); //輸出 "Mike,John,Bill" alert(oCar2.drivers); //輸出 "Mike,John"
動態原型方式
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); if (typeof Car._initialized == "undefined") { Car.prototype.showColor = function() { alert(this.color); }; Car._initialized = true; } }