初看原型
prototype源自法語,軟件界的標(biāo)準(zhǔn)翻譯為“原型”,代表事物的初始形態(tài),也含有模型和樣板的意義。JavaScript中的prototype概念恰如其分地反映了這個詞的內(nèi)含,我們不能將其理解為C++的prototype那種預(yù)先聲明的概念。
JavaScript的所有function類型的對象都有一個prototype屬性。這個prototype屬性本身又是一個object類型的對象,因此我們也可以給這個prototype對象添加任意的屬性和方法。既然prototype是對象的“原型”,那么由該函數(shù)構(gòu)造出來的對象應(yīng)該都會具有這個“原型”的特性。事實上,在構(gòu)造函數(shù)的prototype上定義的所有屬性和方法,都是可以通過其構(gòu)造的對象直接訪問和調(diào)用的。也可以這么說,prototype提供了一群同類對象共享屬性和方法的機(jī)制。
我們先來看看下面的代碼:
function Person(name) { this.name = name; //設(shè)置對象屬性,每個對象各自一份屬性數(shù)據(jù) }; Person.prototype.SayHello = function() //給Person函數(shù)的prototype添加SayHello方法。 { alert("Hello, I'm " + this.name); }
var BillGates = new Person("Bill Gates"); //創(chuàng)建BillGates對象 var SteveJobs = new Person("Steve Jobs"); //創(chuàng)建SteveJobs對象
BillGates.SayHello(); //通過BillGates對象直接調(diào)用到SayHello方法 SteveJobs.SayHello(); //通過SteveJobs對象直接調(diào)用到SayHello方法
alert(BillGates.SayHello == SteveJobs.SayHello); //因為兩個對象是共享prototype的SayHello,所以顯示:true
程序運行的結(jié)果表明,構(gòu)造函數(shù)的prototype上定義的方法確實可以通過對象直接調(diào)用到,而且代碼是共享的。顯然,把方法設(shè)置到prototype的寫法顯得優(yōu)雅多了,盡管調(diào)用形式?jīng)]有變,但邏輯上卻體現(xiàn)了方法與類的關(guān)系,相對前面的寫法,更容易理解和組織代碼。
那么,對于多層次類型的構(gòu)造函數(shù)情況又如何呢?
我們再來看下面的代碼:
1 function Person(name) //基類構(gòu)造函數(shù) 2 { 3 this.name = name; 4 }; 5 6 Person.prototype.SayHello = function() //給基類構(gòu)造函數(shù)的prototype添加方法 7 { 8 alert("Hello, I'm " + this.name); 9 }; 10 11 function Employee(name, salary) //子類構(gòu)造函數(shù) 12 { 13 Person.call(this, name); //調(diào)用基類構(gòu)造函數(shù) 14 this.salary = salary; 15 }; 16 17 Employee.prototype = new Person(); //建一個基類的對象作為子類原型的原型,這里很有意思 18 19 Employee.prototype.ShowMeTheMoney = function() //給子類添構(gòu)造函數(shù)的prototype添加方法 20 { 21 alert(this.name + " $" + this.salary); 22 }; 23 24 var BillGates = new Person("Bill Gates"); //創(chuàng)建基類Person的BillGates對象 25 var SteveJobs = new Employee("Steve Jobs", 1234); //創(chuàng)建子類Employee的SteveJobs對象 26 27 BillGates.SayHello(); //通過對象直接調(diào)用到prototype的方法 28 SteveJobs.SayHello(); //通過子類對象直接調(diào)用基類prototype的方法,關(guān)注! 29 SteveJobs.ShowMeTheMoney(); //通過子類對象直接調(diào)用子類prototype的方法 30 31 alert(BillGates.SayHello == SteveJobs.SayHello); //顯示:true,表明prototype的方法是共享的
這段代碼的第17行,構(gòu)造了一個基類的對象,并將其設(shè)為子類構(gòu)造函數(shù)的prototype,這是很有意思的。這樣做的目的就是為了第28行,通過子類對象也可以直接調(diào)用基類prototype的方法。為什么可以這樣呢?
原來,在JavaScript中,prototype不但能讓對象共享自己財富,而且prototype還有尋根問祖的天性,從而使得先輩們的遺產(chǎn)可以代代相傳。當(dāng)從一個對象那里讀取屬性或調(diào)用方法時,如果該對象自身不存在這樣的屬性或方法,就會去自己關(guān)聯(lián)的prototype對象那里尋找;如果prototype沒有,又會去prototype自己關(guān)聯(lián)的前輩prototype那里尋找,直到找到或追溯過程結(jié)束為止。
在JavaScript內(nèi)部,對象的屬性和方法追溯機(jī)制是通過所謂的prototype鏈來實現(xiàn)的。當(dāng)用new操作符構(gòu)造對象時,也會同時將構(gòu)造函數(shù)的prototype對象指派給新創(chuàng)建的對象,成為該對象內(nèi)置的原型對象。對象內(nèi)置的原型對象應(yīng)該是對外不可見的,盡管有些瀏覽器(如Firefox)可以讓我們訪問這個內(nèi)置原型對象,但并不建議這樣做。內(nèi)置的原型對象本身也是對象,也有自己關(guān)聯(lián)的原型對象,這樣就形成了所謂的原型鏈。
在原型鏈的最末端,就是Object構(gòu)造函數(shù)prototype屬性指向的那一個原型對象。這個原型對象是所有對象的最老祖先,這個老祖宗實現(xiàn)了諸如toString等所有對象天生就該具有的方法。其他內(nèi)置構(gòu)造函數(shù),如Function, Boolean, String, Date和RegExp等的prototype都是從這個老祖宗傳承下來的,但他們各自又定義了自身的屬性和方法,從而他們的子孫就表現(xiàn)出各自宗族的那些特征。
這不就是“繼承”嗎?是的,這就是“繼承”,是JavaScript特有的“原型繼承”。
“原型繼承”是慈祥而又嚴(yán)厲的。原形對象將自己的屬性和方法無私地貢獻(xiàn)給孩子們使用,也并不強(qiáng)迫孩子們必須遵從,允許一些頑皮孩子按自己的興趣和愛好獨立行事。從這點上看,原型對象是一位慈祥的母親。然而,任何一個孩子雖然可以我行我素,但卻不能動原型對象既有的財產(chǎn),因為那可能會影響到其他孩子的利益。從這一點上看,原型對象又象一位嚴(yán)厲的父親。我們來看看下面的代碼就可以理解這個意思了:
function Person(name) { this.name = name; }; Person.prototype.company = "Microsoft"; //原型的屬性 Person.prototype.SayHello = function() //原型的方法 { alert("Hello, I'm " + this.name + " of " + this.company); }; var BillGates = new Person("Bill Gates"); BillGates.SayHello(); //由于繼承了原型的東西,規(guī)規(guī)矩矩輸出:Hello, I'm Bill Gates var SteveJobs = new Person("Steve Jobs"); SteveJobs.company = "Apple"; //設(shè)置自己的company屬性,掩蓋了原型的company屬性 SteveJobs.SayHello = function() //實現(xiàn)了自己的SayHello方法,掩蓋了原型的SayHello方法 { alert("Hi, " + this.name + " like " + this.company + ", ha ha ha "); };
SteveJobs.SayHello(); //都是自己覆蓋的屬性和方法,輸出:Hi, Steve Jobs like Apple, ha ha ha BillGates.SayHello(); //SteveJobs的覆蓋沒有影響原型對象,BillGates還是按老樣子輸出
對象可以掩蓋原型對象的那些屬性和方法,一個構(gòu)造函數(shù)原型對象也可以掩蓋上層構(gòu)造函數(shù)原型對象既有的屬性和方法。這種掩蓋其實只是在對象自己身上創(chuàng)建了新的屬性和方法,只不過這些屬性和方法與原型對象的那些同名而已。JavaScript就是用這簡單的掩蓋機(jī)制實現(xiàn)了對象的“多態(tài)”性,與靜態(tài)對象語言的虛函數(shù)和重載(override)概念不謀而合。
然而,比靜態(tài)對象語言更神奇的是,我們可以隨時給原型對象動態(tài)添加新的屬性和方法,從而動態(tài)地擴(kuò)展基類的功能特性。這在靜態(tài)對象語言中是很難想象的。我們來看下面的代碼:
function Person(name) { this.name = name; }; Person.prototype.SayHello = function() //建立對象前定義的方法 { alert("Hello, I'm " + this.name); }; var BillGates = new Person("Bill Gates"); //建立對象 BillGates.SayHello(); Person.prototype.Retire = function() //建立對象后再動態(tài)擴(kuò)展原型的方法 { alert("Poor " + this.name + ", bye bye!"); }; BillGates.Retire(); //動態(tài)擴(kuò)展的方法即可被先前建立的對象立即調(diào)用
阿彌佗佛,原型繼承竟然可以玩出有這樣的法術(shù)!
出處:軟件真諦
責(zé)任編輯:moby
上一頁 構(gòu)造對象 下一頁 原型擴(kuò)展
◎進(jìn)入論壇網(wǎng)頁制作、WEB標(biāo)準(zhǔn)化版塊參加討論,我還想發(fā)表評論。
|