1、直接創建模式。這是最簡單也是最直接的一種模式,首先創建一個引用類型的對象,然後為其添加自定義屬性和方法。示例代碼如下:
復制代碼 代碼如下:
var person = new Object();
person.name = "Sam";
person.age = 16;
person.speak = function(){
alert(this.name + "is " + this.age + "years old");
}
person.speak();
可以看到,上面創建了一個Object類型的對象,然後為其添加了name和age屬性以及一個speak方法。直接創建模式雖然簡單,但其缺點是顯而易見的:當我們需要創建許多相同的對象時,每次都要重復編寫代碼。為了解決這個問題,我們可以將創建對象的過程進行封裝,於是便有了下面的工廠模式。
2、工廠模式。工廠模式是程序設計中一種常用的設計模式,它主要是將創建對象的過程進行了封裝,示例代碼如下:
復制代碼 代碼如下:
function createPerson(name, age){
var person = new Object();
person.name = name;
person.age = age;
person.speak = function(){
alert(this.name + "is " + this.age + "years old");
}
return person;
}
var person1 = createPerson("Sam", 16);
var person2 = createPerson("Jack", 18);
使用工廠模式後,創建相同類型的對象變得簡單了。但工廠模式沒有解決對象識別的問題,即我們無法確定創建的對象的具體類型。有過面向對象編程經驗的開發人員都知道,對象的創建應當基於類,有了具體的自定義類,再來創建該類的對象。幸好,在JavaScript中,我們可以通過構造函數模式來模擬一個類。
3、構造函數模式。構造函數和普通函數沒有任何區別。任何普通函數都可以作為構造函數,只要使用new操作符即可;任何構造函數也都可以作為普通函數來調用。只不過在JavaScript中,有一個約定,就是用作構造函數的函數名需要首字母大寫。示例代碼如下:
復制代碼 代碼如下:
function Person(name, age){
this.name = name;
this.age = age;
this.speak = function(){
alert(this.name + "is " + this.age + "years old");
}
}
var person1 = new Person("Sam", 16);
var person2 = new Person("Jack", 18);
可以看到,在構造函數內部,我們使用了this來添加屬性和方法,那麼,這個this是指什麼呢?當我們創建了一個Person的對象時,this即是指這個創建的對象。現在,我們可以識別出對象person1和person2的具體類型了。使用alert(person1 instanceOf Person)後可以發現,輸出的值為true。但構造函數模式也有自己的缺點,就是構造函數內聲明的方法在每次創建新對象時都會重新創建(在JavaScript中,函數也是對象)。也就是說,構造函數內的方法是與對象綁定的,而不是與類綁定的。下面代碼的輸出可以驗證我們的推斷。
alert(person1.speak == person2.speak); // false 解決這個缺點的一種比較簡單的方法就是將函數的聲明放到構造函數的外面,即:
復制代碼 代碼如下:
function Person(name, age){
this.name = name;
this.age = age;
this.speak = speak;
}
function speak(){
alert(this.name + "is " + this.age + "years old");
}
var person1 = new Person("Sam", 16);
var person2 = new Person("Jack", 18);
alert(person1.speak == person2.speak); // true
問題解決了,但這種方法又帶來了新的問題。首先,函數speak是在全局作用域中聲明的,但它卻只能被用於Person構造函數,放在全局作用域中有被誤用的風險;其次,如果一個自定義類型有很多的方法,則需要聲明很多的全局函數,這既將導致全局作用域的污染,也不利於代碼的封裝。那麼,有沒有什麼辦法能讓自定義類型的方法成為與類綁定的,又不污染全局作用域呢?答案是使用原型模式。
4、原型模式。在我們聲明一個新的函數後,該函數(在JavaScript中,函數也是對象)就會擁有一個prototype的屬性。prototype是一個對象,表示會被該函數創建的所有對象擁有的公共屬性和方法。示例代碼如下:
復制代碼 代碼如下:
function Person(){}
Person.prototype.name="Sam";
Person.prototype.age=16;
Person.prototype.speak = function(){
alert(this.name + "is " + this.age + "years old");
}
var person1 = new Person();
person1.speak();
var person2 = new Person();
alert(person1.speak == person2.speak); // true
可以看到,雖然構造函數內沒有聲明speak方法,但我們創建的對象person1還是能調用speak方法,這是因為JavaScript有一個搜索規則,先搜索實例屬性和方法,找到則返回;如果沒找到,則再到prototype中去搜索。原型模式使得方法是與類相關的,並且沒有污染全局作用域,但其也有自身的缺點:一是所有屬性也都與類相關,這意味著所有對象共享一份屬性,這顯然是不合理的;二是沒有辦法向構造函數傳入初始化數據了。解決的方法很簡單,就是混合使用構造函數模式和原型模式。
5、組合模式。示例代碼如下:
復制代碼 代碼如下:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.speak = function(){
alert(this.name + "is " + this.age + "years old");
}
var person1 = new Person();
person1.speak();
var person2 = new Person();
alert(person1.speak == person2.speak); // true
不難發現,組合模式實現了我們的所有需求,這也是目前應用得比較廣泛的一種模式。有面向對象編程經驗的開發人員可能會覺得將prototype的聲明放在構造函數外面有點別扭,那麼能否將其放到構造函數裡去呢?答案是肯定的,使用動態組合模式即可。
6、動態組合模式。其原理就是先判斷原型中的某個屬性或方法是不是已經聲明過,如果沒有聲明,則聲明整個原型;否則,什麼也不用做。示例代碼如下:
復制代碼 代碼如下:
function Person(name, age){
this.name = name;
this.age = age;
if (Person.prototype.speak == "undefined"){
Person.prototype.speak = function(){
alert(this.name + "is " + this.age + "years old");
}
}
}