原型真諦
正當(dāng)我們感概萬分時(shí),天空中一道紅光閃過,祥云中出現(xiàn)了觀音菩薩。只見她手持玉凈瓶,輕拂翠柳枝,灑下幾滴甘露,頓時(shí)讓JavaScript又添新的靈氣。
觀音灑下的甘露在JavaScript的世界里凝結(jié)成塊,成為了一種稱為“語法甘露”的東西。這種語法甘露可以讓我們編寫的代碼看起來更象對(duì)象語言。
要想知道這“語法甘露”為何物,就請(qǐng)君側(cè)耳細(xì)聽。
在理解這些語法甘露之前,我們需要重新再回顧一下JavaScript構(gòu)造對(duì)象的過程。
我們已經(jīng)知道,用 var anObject = new aFunction() 形式創(chuàng)建對(duì)象的過程實(shí)際上可以分為三步:第一步是建立一個(gè)新對(duì)象;第二步將該對(duì)象內(nèi)置的原型對(duì)象設(shè)置為構(gòu)造函數(shù)prototype引用的那個(gè)原型對(duì)象;第三步就是將該對(duì)象作為this參數(shù)調(diào)用構(gòu)造函數(shù),完成成員設(shè)置等初始化工作。對(duì)象建立之后,對(duì)象上的任何訪問和操作都只與對(duì)象自身及其原型鏈上的那串對(duì)象有關(guān),與構(gòu)造函數(shù)再扯不上關(guān)系了。換句話說,構(gòu)造函數(shù)只是在創(chuàng)建對(duì)象時(shí)起到介紹原型對(duì)象和初始化對(duì)象兩個(gè)作用。
那么,我們能否自己定義一個(gè)對(duì)象來當(dāng)作原型,并在這個(gè)原型上描述類,然后將這個(gè)原型設(shè)置給新創(chuàng)建的對(duì)象,將其當(dāng)作對(duì)象的類呢?我們又能否將這個(gè)原型中的一個(gè)方法當(dāng)作構(gòu)造函數(shù),去初始化新建的對(duì)象呢?例如,我們定義這樣一個(gè)原型對(duì)象:
var Person = //定義一個(gè)對(duì)象來作為原型類 { Create: function(name, age) //這個(gè)當(dāng)構(gòu)造函數(shù) { this.name = name; this.age = age; }, SayHello: function() //定義方法 { alert("Hello, I'm " + this.name); }, HowOld: function() //定義方法 { alert(this.name + " is " + this.age + " years old."); } };
這個(gè)JSON形式的寫法多么象一個(gè)C#的類!既有構(gòu)造函數(shù),又有各種方法。如果可以用某種形式來創(chuàng)建對(duì)象,并將對(duì)象的內(nèi)置的原型設(shè)置為上面這個(gè)“類”對(duì)象,不就相當(dāng)于創(chuàng)建該類的對(duì)象了嗎?
但遺憾的是,我們幾乎不能訪問到對(duì)象內(nèi)置的原型屬性!盡管有些瀏覽器可以訪問到對(duì)象的內(nèi)置原型,但這樣做的話就只能限定了用戶必須使用那種瀏覽器。這也幾乎不可行。
那么,我們可不可以通過一個(gè)函數(shù)對(duì)象來做媒介,利用該函數(shù)對(duì)象的prototype屬性來中轉(zhuǎn)這個(gè)原型,并用new操作符傳遞給新建的對(duì)象呢?
其實(shí),象這樣的代碼就可以實(shí)現(xiàn)這一目標(biāo):
function anyfunc(){}; //定義一個(gè)函數(shù)軀殼 anyfunc.prototype = Person; //將原型對(duì)象放到中轉(zhuǎn)站prototype var BillGates = new anyfunc(); //新建對(duì)象的內(nèi)置原型將是我們期望的原型對(duì)象
不過,這個(gè)anyfunc函數(shù)只是一個(gè)軀殼,在使用過這個(gè)軀殼之后它就成了多余的東西了,而且這和直接使用構(gòu)造函數(shù)來創(chuàng)建對(duì)象也沒啥不同,有點(diǎn)不爽。
可是,如果我們將這些代碼寫成一個(gè)通用函數(shù),而那個(gè)函數(shù)軀殼也就成了函數(shù)內(nèi)的函數(shù),這個(gè)內(nèi)部函數(shù)不就可以在外層函數(shù)退出作用域后自動(dòng)消亡嗎?而且,我們可以將原型對(duì)象作為通用函數(shù)的參數(shù),讓通用函數(shù)返回創(chuàng)建的對(duì)象。我們需要的就是下面這個(gè)形式:
function New(aClass, aParams) //通用創(chuàng)建函數(shù) { function new_() //定義臨時(shí)的中轉(zhuǎn)函數(shù)殼 { aClass.Create.apply(this, aParams); //調(diào)用原型中定義的的構(gòu)造函數(shù),中轉(zhuǎn)構(gòu)造邏輯及構(gòu)造參數(shù) }; new_.prototype = aClass; //準(zhǔn)備中轉(zhuǎn)原型對(duì)象 return new new_(); //返回建立最終建立的對(duì)象 }; var Person = //定義的類 { Create: function(name, age) { this.name = name; this.age = age; }, SayHello: function() { alert("Hello, I'm " + this.name); }, HowOld: function() { alert(this.name + " is " + this.age + " years old."); } }; var BillGates = New(Person, ["Bill Gates", 53]); //調(diào)用通用函數(shù)創(chuàng)建對(duì)象,并以數(shù)組形式傳遞構(gòu)造參數(shù) BillGates.SayHello(); BillGates.HowOld();
alert(BillGates.constructor == Object); //輸出:true
這里的通用函數(shù)New()就是一個(gè)“語法甘露”!這個(gè)語法甘露不但中轉(zhuǎn)了原型對(duì)象,還中轉(zhuǎn)了構(gòu)造函數(shù)邏輯及構(gòu)造參數(shù)。
有趣的是,每次創(chuàng)建完對(duì)象退出New函數(shù)作用域時(shí),臨時(shí)的new_函數(shù)對(duì)象會(huì)被自動(dòng)釋放。由于new_的prototype屬性被設(shè)置為新的原型對(duì)象,其原來的原型對(duì)象和new_之間就已解開了引用鏈,臨時(shí)函數(shù)及其原來的原型對(duì)象都會(huì)被正確回收了。上面代碼的最后一句證明,新創(chuàng)建的對(duì)象的constructor屬性返回的是Object函數(shù)。其實(shí)新建的對(duì)象自己及其原型里沒有constructor屬性,那返回的只是最頂層原型對(duì)象的構(gòu)造函數(shù),即Object。
有了New這個(gè)語法甘露,類的定義就很像C#那些靜態(tài)對(duì)象語言的形式了,這樣的代碼顯得多么文靜而優(yōu)雅!
當(dāng)然,這個(gè)代碼僅僅展示了“語法甘露”的概念。我們還需要多一些的語法甘露,才能實(shí)現(xiàn)用簡(jiǎn)潔而優(yōu)雅的代碼書寫類層次及其繼承關(guān)系。好了,我們?cè)賮砜匆粋(gè)更豐富的示例吧:
//語法甘露: var object = //定義小寫的object基本類,用于實(shí)現(xiàn)最基礎(chǔ)的方法等 { isA: function(aType) //一個(gè)判斷類與類之間以及對(duì)象與類之間關(guān)系的基礎(chǔ)方法 { var self = this; while(self) { if (self == aType) return true; self = self.Type; }; return false; } }; function Class(aBaseClass, aClassDefine) //創(chuàng)建類的函數(shù),用于聲明類及繼承關(guān)系 { function class_() //創(chuàng)建類的臨時(shí)函數(shù)殼 { this.Type = aBaseClass; //我們給每一個(gè)類約定一個(gè)Type屬性,引用其繼承的類 for(var member in aClassDefine) this[member] = aClassDefine[member]; //復(fù)制類的全部定義到當(dāng)前創(chuàng)建的類 }; class_.prototype = aBaseClass; return new class_(); }; function New(aClass, aParams) //創(chuàng)建對(duì)象的函數(shù),用于任意類的對(duì)象創(chuàng)建 { function new_() //創(chuàng)建對(duì)象的臨時(shí)函數(shù)殼 { this.Type = aClass; //我們也給每一個(gè)對(duì)象約定一個(gè)Type屬性,據(jù)此可以訪問到對(duì)象所屬的類 if (aClass.Create) aClass.Create.apply(this, aParams); //我們約定所有類的構(gòu)造函數(shù)都叫Create,這和DELPHI比較相似 }; new_.prototype = aClass; return new new_(); };
//語法甘露的應(yīng)用效果: var Person = Class(object, //派生至object基本類 { Create: function(name, age) { this.name = name; this.age = age; }, SayHello: function() { alert("Hello, I'm " + this.name + ", " + this.age + " years old."); } }); var Employee = Class(Person, //派生至Person類,是不是和一般對(duì)象語言很相似? { Create: function(name, age, salary) { Person.Create.call(this, name, age); //調(diào)用基類的構(gòu)造函數(shù) this.salary = salary; }, ShowMeTheMoney: function() { alert(this.name + " $" + this.salary); } });
var BillGates = New(Person, ["Bill Gates", 53]); var SteveJobs = New(Employee, ["Steve Jobs", 53, 1234]); BillGates.SayHello(); SteveJobs.SayHello(); SteveJobs.ShowMeTheMoney(); var LittleBill = New(BillGates.Type, ["Little Bill", 6]); //根據(jù)BillGate的類型創(chuàng)建LittleBill LittleBill.SayHello(); alert(BillGates.isA(Person)); //true alert(BillGates.isA(Employee)); //false alert(SteveJobs.isA(Person)); //true alert(Person.isA(Employee)); //false alert(Employee.isA(Person)); //true
“語法甘露”不用太多,只要那么一點(diǎn)點(diǎn),就能改觀整個(gè)代碼的易讀性和流暢性,從而讓代碼顯得更優(yōu)雅。有了這些語法甘露,JavaScript就很像一般對(duì)象語言了,寫起代碼了感覺也就爽多了!
令人高興的是,受這些甘露滋養(yǎng)的JavaScript程序效率會(huì)更高。因?yàn)槠湓蛯?duì)象里既沒有了毫無用處的那些對(duì)象級(jí)的成員,而且還不存在constructor屬性體,少了與構(gòu)造函數(shù)間的牽連,但依舊保持了方法的共享性。這讓JavaScript在追溯原型鏈和搜索屬性及方法時(shí),少費(fèi)許多工夫啊。
我們就把這種形式稱為“甘露模型”吧!其實(shí),這種“甘露模型”的原型用法才是符合prototype概念的本意,才是的JavaScript原型的真諦!
想必微軟那些設(shè)計(jì)AJAX架構(gòu)的工程師看到這個(gè)甘露模型時(shí),肯定后悔沒有早點(diǎn)把AJAX部門從美國(guó)搬到咱中國(guó)的觀音廟來,錯(cuò)過了觀音菩薩的點(diǎn)化。當(dāng)然,我們也只能是在代碼的示例中,把Bill Gates當(dāng)作對(duì)象玩玩,真要讓他放棄上帝轉(zhuǎn)而皈依我佛肯定是不容易的,機(jī)緣未到。∪绻奶炷阍谖④浶鲁龅腁JAX類庫中看到這種甘露模型,那才是真正的緣分!
出處:軟件真諦
責(zé)任編輯:moby
上一頁 原型擴(kuò)展 下一頁 編程的快樂
◎進(jìn)入論壇網(wǎng)頁制作、WEB標(biāo)準(zhǔn)化版塊參加討論,我還想發(fā)表評(píng)論。
|