### 四,原型方式
我們創建的每個函數都有一個通過prototype(原型)屬性,這個屬性是一個對象,它的用途是包含可以由特定類型的所有實例共享的屬性和方法。邏輯上可以這么理解:prototypt通過條用構造函數而創建的那個對象的原型對象。使用原型的好處就是可以讓所有對象實例共享它所包含的屬性和方法。也就是說,不必在構造函數中定義對象信息,而是直接將這些信息添加到原型中。
原型方式利用了對象的prototype 屬性,可以把它看成創建新對象所依賴的原型。這里,首先用空構造函數來設置函數名。然后所有的屬性和方法都被直接賦予prototype屬性。我重寫了前面的例子,代碼如下:
~~~
function Car() { };
//將所有的屬性的方法都賦予prototype屬性
Car.prototype.color = "blue";
Car.prototype.doors = 4;
Car.prototype.mpg = 25;
Car.prototype.showColor = function() {
return this.color;
};
var Car1 = new Car();
var Car2 = new Car();
document.write(Car1.showColor()+"<br/>");//輸出:blue
document.write(Car2.showColor());//輸出:blue
~~~
在這段代碼中,首先定義構造函數Car(),其中無任何代碼。接下來的幾行代碼,通過給Car的?prototype 屬性添加屬性去定義Car對象的屬性。調用new Car()時,原型的所有屬性都被立即賦予要創建的對象,意味著所有Car實例存放的都是指向 showColor() 函數的指針。從語義上講,所有屬性看起來都屬于一個對象,因此解決了前面的工廠方式和構造函數方式存在的問題。
此外,使用這種方式,還能用 instanceof 運算符檢查給定變量指向的對象的類型:
~~~
<span style="font-size:18px;">document.write(Car1 instanceof Car); //輸出:true</span>
~~~
原型方式看起來是個不錯的解決方案。遺憾的是,它并不盡如人意。首先,這個構造函數沒有參數。使用原型方式,不能通過給構造函數傳遞參數來初始化屬性的值,因為Car1和Car2的color屬性都等于?"blue",doors屬性都等于4,mpg屬性都等于25。這意味著必須在對象創建后才能改變屬性的默認值,這點很令人討厭,但還沒完。真正的問題出現在屬性指向的是對象,而不是函數時。函數共享不會造成問題,但對象卻很少被多個實例共享。請思考下面的
例子:
~~~
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() {
return this.color;
};
var Car1 = new Car();
var Car2 = new Car();
Car1.drivers.push("Bill");
document.write(Car1.drivers+"<br/>");//輸出:Mike,John,Bill
document.write(Car2.drivers);//輸出 :Mike,John,Bill
~~~
上面的代碼中,屬性drivers是指向Array對象的指針,該數組中包含兩個名"Mike"和 "John"。由于?drivers是引用值,Car的兩個實例都指向同一個數組。這意味著給Car1.drivers添加值 "Bill",在?Car2.drivers 中也能看到。輸出這兩個指針中的任何一個,結果都是顯示字符串 "Mike,John,Bill"。由于創建對象時有這么多問題,你一定會想,是否有種合理的創建對象的方法呢?答案是有,需要聯合使用構造函數和原型方式。
### 五,混合的構造函數/原型方式(推薦使用)
混合使用構造函數方式和原型方式,就可像用其他程序設計語言一樣創建對象。這種概念非常簡單,即用構造函數定義對象的所有非函數屬性,用原型方式定義對象的函數屬性(方法)。結果是,所有函數都只創建一次,而每個對象都具有自己的對象屬性實例。我們重寫了前面的例子,代碼如下:
~~~
function Car(Color,Doors,Mpg) {
this.color = Color;
this.doors = Doors;
this.mpg = Mpg;
this.drivers = new Array("Mike","John");
};
Car.prototype.showColor = function() {
return this.color;
};
var Car1 = new Car("red",4,23);
var Car2 = new Car("blue",3,25);
Car1.drivers.push("Bill");
document.write(Car1.drivers+"<br/>");//輸出:Mike,John,Bill
documnet.write(Car2.drivers);//輸出:Mike,John
~~~
現在就更像創建一般對象了。所有的非函數屬性都在構造函數中創建,意味著又能夠用構造函數的參數賦予屬性默認值了。因為只創建showColor()函數的一個實例,所以沒有內存浪費。此外,給Car1的drivers數組添加 "Bill" 值,不會影響到Car2的數組,所以輸出這些數組的值時,Car1.drivers 顯示的是?"Mike,John,Bill",而 Car2.drivers 顯示的是 "Mike,John"。因為使用了原型方式,所以仍然能利用?instanceof運算符來判斷對象的類型。
這種方式是ECMAScript采用的主要方式,它具有其他方式的特性,卻沒有他們的副作用。不過,有些開發者仍覺得這種方法不夠完美。
### 六,動態原型方式
對于習慣使用其他語言的開發者來說,使用混合的構造函數/原型方式感覺不那么和諧。畢竟,定義類時,大多數面向對象語言都對屬性和方法進行了視覺上的封裝。請考慮下面的 Java 類:
~~~
class Car {
public String color = "blue";
public int doors = 4;
public int mpg = 25;
public Car(String color, int doors, int mpg) {
this.color = color;
this.doors = doors;
this.mpg = mpg;
}
public void showColor() {
System.out.println(color);
}
}
~~~
Java很好地打包了Car類的所有屬性和方法,因此看見這段代碼就知道它要實現什么功能,它定義了一個對象的信息。批評混合的構造函數/原型方式的人認為,在構造函數內部找屬性,在其外部找方法的做法不合邏輯。因此,他們設計了動態原型方法,以提供更友好的編碼風格。
動態原型方法的基本想法與混合的構造函數/原型方式相同,即在構造函數內定義非函數屬性,而函數屬性則利用原型屬性定義。唯一的區別是賦予對象方法的位置。下面是用動態原型方法重寫的Car:
~~~
function Car(Color,Doors,Mpg) {
this.color = Color;
this.doors = Doors;
this.mpg = Mpg;
this.drivers = new Array("Mike","John");
//如果Car對象中的_initialized為undefined,表明還沒有為Car的原型添加方法
if (typeof Car._initialized == "undefined") {
Car.prototype.showColor = function() {
return this.color;
};
Car._initialized = true; //設置為true,不必再為prototype添加方法
}
}
var Car1 = new Car("red",4,23);//生成一個Car對象
var Car2 = new Car("blue",3,25);
Car1.drivers.push("Bill");//向Car1對象實例的drivers屬性添加一個元素
document.write(Car1.drivers+"<br/>");//輸出:Mike,John,Bill
document.write(Car2.drivers);//輸出:Mike,John
~~~
直到檢查typeof Car._initialize是否等于"undefined"之前,這個構造函數都未發生變化。這行代碼是動態原型方法中最重要的部分。如果這個值未定義,構造函數將用原型方式繼續定義對象的方法,然后把?Car._initialized設置為true。如果這個值定義了(它的值為 true時,typeof 的值為Boolean),那么就不再創建該方法。簡而言之,該方法使用標志(_initialized)來判斷是否已給原型賦予了任何方法。該方法只創建并賦值一次,傳統的 OOP開發者會高興地發現,這段代碼看起來更像其他語言中的類定義了。
我們應該采用哪種方式呢?
如前所述,目前使用最廣泛的是混合的構造函數/原型方式。此外,動態原型方式也很流行,在功能上與構造函數/原型方式等價。可以采用這兩種方式中的任何一種。不過不要單獨使用經典的構造函數或原型方式,因為這樣會給代碼引入問題。總之JS是基于面向對象的一門客戶端腳本語言,我們在學習它的面向對象技術的時候要的留意JS與其他嚴謹性高的程序語言的不同。也要正確使用JS創建對象的合理的方式,推薦使用構造函數與原型方式的混合方式創建對象實例。這樣可以避免許多不必要的麻煩。
- 前言
- HTML學習1:Dreamweaver8的安裝
- HTML學習2:初識HTML
- HTML學習3:常用標簽之文本標簽
- HTML學習4:常用標簽之列表標簽
- HTML學習5:常用標簽之圖像標簽
- HTML學習6:常用標簽之超鏈接標簽
- HTML學習7:常用標簽之表格標簽
- HTML學習8:常用標簽之框架標簽
- HTML學習9:常用標簽之表單標簽
- HTML學習10:表單格式化
- HTML學習11:HTTP 方法
- HTML學習12:其他常見標簽之頭標簽
- HTML學習13:其他常見標簽之體標簽
- 輕松學習JavaScript一:為什么學習JavaScript
- 輕松學習JavaScript二:JavaScript語言的基本語法要求
- 輕松學習JavaScript三:JavaScript與HTML的結合
- 輕松學習JavaScript四:JS點擊燈泡來點亮或熄滅這盞燈的網頁特效映射出JS在HTML中作用
- 輕松學習JavaScript五:JavaScript的變量和數據類型
- 輕松學習JavaScript六:JavaScript的表達式與運算符
- 輕松學習JavaScript七:JavaScript的流程控制語句
- 輕松學習JavaScript八:JavaScript函數
- 輕松學習JavaScript九:JavaScript對象和數組
- 輕松學習JavaScript十:JavaScript的Date對象制作一個簡易鐘表
- 輕松學習JavaScript十一:JavaScript基本類型(包含類型轉換)和引用類型
- 輕松學習JavaScript十二:JavaScript基于面向對象之創建對象(一)
- 輕松學習JavaScript十二:JavaScript基于面向對象之創建對象(二)
- 輕松學習JavaScript十三:JavaScript基于面向對象之繼承(包含面向對象繼承機制)
- 輕松學習JavaScript十四:JavaScript的RegExp對象(正則表達式)
- 輕松學習JavaScript十五:JavaScript之BOM簡介
- 輕松學習JavaScript十六:JavaScript的BOM學習(一)
- 輕松學習JavaScript十七:JavaScript的BOM學習(二)
- 輕松學習JavaScript二十九:JavaScript中的this詳解
- CSS基礎學習一:CSS概述
- CSS基礎學習二:如何創建 CSS
- CSS基礎學習三:CSS語法
- CSS基礎學習四:元素選擇器
- CSS基礎學習五:類選擇器
- CSS基礎學習六:id選擇器
- CSS基礎學習七:屬性選擇器
- CSS基礎學習八:派生選擇器
- CSS基礎學習九:偽類
- CSS基礎學習十:偽元素
- CSS基礎學習十一:選擇器的優先級
- CSS基礎學習十二:CSS樣式
- CSS基礎學習十三:盒子模型
- CSS基礎學習十四:盒子模型補充之display屬性設置
- CSS基礎學習十五:盒子模型補充之外邊距合并
- CSS基礎學習十六:CSS盒子模型補充之border-radius屬性
- CSS基礎學習十七:CSS布局之定位
- CSS基礎學習十八:CSS布局之浮動
- CSS基礎學習十九:CSS布局之圖文混排,圖像簽名,多圖拼接和圖片特效
- DIV+CSS實操一:經管系網頁總體模塊布局
- DIV+CSS實操二:經管系網頁添加導航欄和友情鏈接欄
- DIV+CSS實操三:經管系網頁內容模塊添加標題欄和版權信息模塊
- DIV+CSS實操四:經管系網頁內容模塊內容添加(一)
- DIV+CSS實操五:經管系網頁內容模塊內容添加(二)
- DIV+CSS實操六:經管系網頁添加導航欄下拉菜單
- DIV+CSS實操七:中文系內容模塊控制文本不換行和超出指定寬度后用省略號代替
- JS中實現字符串和數組的相互轉化