在學(xué)習(xí)《JavaScript高級程序設(shè)計》(第3版)第六章創(chuàng)建對象時齿穗,遇到了針對創(chuàng)建自定義類型對象的幾種設(shè)計模式吠昭。其中的工廠模式與寄生構(gòu)造函數(shù)模式以及穩(wěn)妥構(gòu)造函數(shù)模式三者在實現(xiàn)上十分相似喊括,但卻具有微妙的差別,所以對它們做一個總結(jié)矢棚。
一郑什、工廠模式
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
}
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
工廠模式顧名思義,就是通過定義一個通用的函數(shù)蒲肋,將對象的所有創(chuàng)建工作都封裝到這個函數(shù)中蘑拯。之后每當(dāng)需要創(chuàng)建一個對象時钝满,只需要調(diào)用這個函數(shù),同時給出初始化對象所需的各個參數(shù)申窘,就能自動返回創(chuàng)建好的對象弯蚜。這就如同工廠里批量生產(chǎn)一件件產(chǎn)品一般,因為創(chuàng)建出的所有對象之間雖然內(nèi)容不同剃法,但都出自同一模板碎捺。
二、寄生構(gòu)造函數(shù)模式
function Person(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
}
return o;
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
寄生構(gòu)造函數(shù)模式與工廠模式極為相似贷洲,區(qū)別在于:
- 寄生構(gòu)造函數(shù)模式將工廠模式中的那個通用函數(shù)
createPerson()
改名為Person()
收厨,并將其看作為對象的構(gòu)造函數(shù)。 - 創(chuàng)建對象實例時优构,寄生構(gòu)造函數(shù)模式采用
new
操作符
那么兩者有什么功能上的差別呢诵叁?事實上,兩者本質(zhì)上的差別僅在于new
操作符(因為函數(shù)取什么名字無關(guān)緊要)俩块,工廠模式創(chuàng)建對象時將createPerson
看作是普通的函數(shù)黎休,而寄生構(gòu)造函數(shù)模式創(chuàng)建對象時將Person
看作是構(gòu)造函數(shù),不過這對于創(chuàng)建出的對象來說玉凯,沒有任何差別势腮。
對于兩者的差別,作者在書中是這么說的:
除了使用new操作符并把使用的包裝函數(shù)叫做構(gòu)造函數(shù)之外漫仆,這個模式跟工廠模式其實是一模一樣的捎拯。構(gòu)造函數(shù)在不返回值的情況下,默認會返回新對象實例盲厌。而通過在構(gòu)造函數(shù)的末尾添加一個return語句署照,可以重寫調(diào)用構(gòu)造函數(shù)時返回的值。
根據(jù)作者的意思吗浩,構(gòu)造函數(shù)和普通函數(shù)的區(qū)別在于:當(dāng)使用new
+構(gòu)造函數(shù)創(chuàng)建對象時建芙,如果構(gòu)造函數(shù)內(nèi)部沒有return
語句,那么默認情況下構(gòu)造函數(shù)將返回一個該類型的實例(如果以上面的例子為參考懂扼,person1和person2為Person
類型的對象實例禁荸,可以使用person1 instanceof Person檢驗),但如果構(gòu)造函數(shù)內(nèi)部通過return
語句返回了一個其它類型的對象實例阀湿,那么這種默認的設(shè)置將被打破赶熟,構(gòu)造函數(shù)最終返回的實例類型將以return
語句中對象實例的類型為準。
基于這個規(guī)則陷嘴,在Person()
構(gòu)造函數(shù)中映砖,由于最后通過return
語句返回了一個Object
類型的對象實例,所以通過該構(gòu)造函數(shù)創(chuàng)建的對象實際上是Object
類型而不是Person
類型灾挨;這樣一來就與createPerson()
函數(shù)返回的對象類型相同邑退,因此可以說工廠模式和寄生構(gòu)造函數(shù)模式在功能上是等價的竹宋。
如果非要說兩者的不同,并且要從其中選擇一個作為創(chuàng)建對象的方法的話地技,我個人更偏向于寄生構(gòu)造函數(shù)模式一些逝撬。這是因為new Person()
(寄生構(gòu)造函數(shù)模式)更能讓我感覺到自己正在創(chuàng)建一個對象,而不是在調(diào)用一個函數(shù)(工廠模式)乓土。
三、穩(wěn)妥構(gòu)造函數(shù)模式
function Person(para_name, para_age, para_job) {
//創(chuàng)建要返回的對象
var o = {};
//在這里定義私有屬性和方法
var name = para_name;
var age = para_age;
var job = para_job;
var sayAge = function() {
alert(age);
};
//在這里定義公共方法
o.sayName = function() {
alert(name);
};
//返回對象
return o;
}
var person1 = Person("Nicholas", 29, "Software Engineer"); //創(chuàng)建對象實例
person1.sayName(); //Nicholas
person1.name; //undefined
person1.sayAge(); // 報錯
穩(wěn)妥構(gòu)造函數(shù)模式與前面介紹的兩種設(shè)計模式具有相似的地方溯警,都是在函數(shù)內(nèi)部定義好對象之后返回該對象來創(chuàng)建實例趣苏。然而穩(wěn)妥構(gòu)造函數(shù)模式的獨特之處在于具有以下特點:
- 沒有通過對象定義公共屬性
- 在公共方法中不使用this引用對象自身
- 不使用new操作符調(diào)用構(gòu)造函數(shù)
這種設(shè)計模式最適合在一些安全的環(huán)境中使用(這些環(huán)境中會禁止使用this和new);為了較好地理解這種設(shè)計模式梯轻,我們可以采取類比的方法——這種構(gòu)造對象的方式就如同C++/Java語言中通過訪問控制符private
定義出包含私有成員的類的方式一樣(將上例按C++中類的方式來定義):
class Person {
//定義私有成員變量和函數(shù)
private:
string name;
int age;
string job;
int sayAge() {return age;}
//定義構(gòu)造函數(shù)和公共方法(函數(shù))
public:
string sayName() {return name;} //公共方法
Person(string p_name, int p_age, string p_job):name(p_name),age(p_age),job(p_job) {} //構(gòu)造函數(shù)
}
//創(chuàng)建對象實例
Person person1("Nicholas", 29, "Software Engineer");
person1.sayName(); //Nicholas
person1.name; //報錯(無法訪問)
person1.sayAge(); //報錯(無法訪問)
可見食磕,利用C++定義出了一個Person
類,其中的name
喳挑、age
彬伦、job
以及sayAge()
是私有成員,無法通過類似person1.name
的方式直接訪問伊诵,這是一種類的保護機制单绑;而定義為public
的sayName()
函數(shù)則可以直接訪問。
JS中的穩(wěn)妥構(gòu)造函數(shù)模式正是為了實現(xiàn)這樣的數(shù)據(jù)保護機制曹宴。它巧妙地利用了函數(shù)的作用域?qū)崿F(xiàn)了對象屬性的私有化:在函數(shù)中定義的變量是局部變量搂橙,按道理本應(yīng)該在函數(shù)執(zhí)行完畢退出后進行銷毀或清理,但由于通過對象的公共方法對該局部變量保持著引用笛坦,所以該變量即便是在構(gòu)造函數(shù)退出之后也依然保持有效(閉包)区转。
這樣一來,創(chuàng)建出的對象既能通過公共方法提供的訪問接口對私有屬性進行訪問(引用的是構(gòu)造函數(shù)的局部變量)版扩,也能保證無法通過對象自身對其直接訪問(person1.name
無法訪問到對應(yīng)數(shù)據(jù)废离,因為name
是構(gòu)造函數(shù)的局部變量而不是對象的屬性),從而保證了對象屬性的訪問安全礁芦。