一、講個(gè)故事吧
澄清在先,Java 和Javascript是雷鋒和雷峰塔的關(guān)系。Javascript原名Mocha,當(dāng)時(shí)還叫做LiveScript,創(chuàng)造者是Brendan Eich,現(xiàn)任Mozilla公司首席技術(shù)官。
1994年,歷史上第一個(gè)比較成熟的網(wǎng)絡(luò)瀏覽器——Navigator0.9版誕生在網(wǎng)景公司(Netscape),極為轟動(dòng)。
但是,Navigator0.9只能用來(lái)瀏覽,不具備與訪問(wèn)者交互的能力,比如,用戶提交一個(gè)數(shù)據(jù)表單,如果表單為空,瀏覽器是無(wú)法判斷的,只能直接提交給服務(wù)器端,再把空值的錯(cuò)誤返回,讓用戶重新填寫,這樣顯然是低效率和浪費(fèi)資源的。
這個(gè)時(shí)候,對(duì)于正處于技術(shù)革新最前沿的 Netscape,開(kāi)發(fā)一種實(shí)用的客戶端腳本語(yǔ)言來(lái)處理這些問(wèn)題變得必要起來(lái),于是,這個(gè)任務(wù)落到了工程師Brendan Eich身上。他覺(jué)得吧,木必要設(shè)計(jì)得很復(fù)雜,只要能搞定一些簡(jiǎn)單操作就夠了,比如判斷用戶有沒(méi)有填寫表單。
1994年正是面向?qū)ο缶幊蹋╫bject-oriented programming)的興盛時(shí)代,C++是最流行的語(yǔ)言,而Java語(yǔ)言的1.0版即將于次年推出,Brendan Eich難免受其影響, 他想將Javascript里面所有的數(shù)據(jù)類型看做是對(duì)象(object),這一點(diǎn)與Java非常相似。但是,他馬上就遇到了一個(gè)難題,到底要不要設(shè)計(jì)”繼承”機(jī)制呢?
二、繼承的演變
1、采用new關(guān)鍵字生成實(shí)例
處理表單驗(yàn)證這樣簡(jiǎn)單功能腳本語(yǔ)言顯然是不需要”繼承”機(jī)制的,然而如果Javascript里面都是對(duì)象,就需要有一種辦法來(lái)把所有對(duì)象聯(lián)系起來(lái)。最后,Brendan Eich還是設(shè)計(jì)了”繼承”。只是,他并沒(méi)有引入”類”(class)的概念,因?yàn)橐坏┯辛?rdquo;類”,Javascript就是一種完整的面向?qū)ο缶幊陶Z(yǔ)言了,
這好像有點(diǎn)太正式了,與設(shè)計(jì)初衷也遠(yuǎn)了,同時(shí)增加了初學(xué)者的入門難度。
參照到C++和Java語(yǔ)言都使用new命令來(lái)生成實(shí)例:
C++這樣寫:
ClassName *object = new ClassName(param);
Java這樣寫:
Foo foo = new Foo();
那么,也可以把new命令引入了Javascript,用來(lái)從原型對(duì)象生成一個(gè)實(shí)例對(duì)象。但是,Javascript中沒(méi)有”類”的話,怎樣表示原型對(duì)象呢?
依然是參照C++和Java使用new命令時(shí),都會(huì)調(diào)用”類”的構(gòu)造函數(shù)(constructor)。Brendan Eich簡(jiǎn)化了設(shè)計(jì),在Javascript語(yǔ)言中,new命令后面跟的是構(gòu)造函數(shù),不再是類。
我們舉個(gè)例子來(lái)說(shuō),現(xiàn)在有一個(gè)叫做WD構(gòu)造函數(shù),表示前端開(kāi)發(fā)(web-developper)對(duì)象的原型。
function WD(skill){
this.skill = skill;
}
對(duì)這個(gè)構(gòu)造函數(shù)使用new關(guān)鍵字,就會(huì)生成一個(gè)前端開(kāi)發(fā)對(duì)象的實(shí)例。
var WD1 = new WD('html');
console.log(WD1.skill); // html
在構(gòu)造函數(shù)中的this關(guān)鍵字,它其實(shí)代表的是新創(chuàng)建的實(shí)例對(duì)象。
2、new 出來(lái)對(duì)象的缺陷
采用new關(guān)鍵字,用構(gòu)造函數(shù)生成實(shí)例對(duì)象無(wú)法共享屬性和方法。
比如,在WD對(duì)象的構(gòu)造函數(shù)中,設(shè)置一個(gè)實(shí)例對(duì)象的共有屬性skill。
function WD(skill){
this.skill = skill;
this.sex = '男';
}
然后,生成兩個(gè)實(shí)例對(duì)象:
var WD1 = new WD('html');
var WD2 = new WD('css');
這兩個(gè)對(duì)象的skill屬性是獨(dú)立的,修改其中一個(gè),不會(huì)影響到另一個(gè)。
WD1.skill= 'Javascript';
console.log(WD2.skill);//“css”,不受WD1的影響
每一個(gè)實(shí)例對(duì)象,都有自己的屬性和方法的副本。這不僅無(wú)法做到數(shù)據(jù)共享,也是極大的資源浪費(fèi)。
3、引入prototype屬性
為了實(shí)現(xiàn)屬性和方法的共享,Brendan Eich決定為構(gòu)造函數(shù)設(shè)置一個(gè)prototype屬性。
這個(gè)屬性包含一個(gè)對(duì)象(以下簡(jiǎn)稱”prototype對(duì)象”),所有實(shí)例對(duì)象需要共享的屬性和方法,都放在這個(gè)對(duì)象里面;那些不需要共享的屬性和方法,就放在構(gòu)造函數(shù)里面。
實(shí)例對(duì)象一旦創(chuàng)建,將自動(dòng)引用prototype對(duì)象的屬性和方法。也就是說(shuō),實(shí)例對(duì)象的屬性和方法,分成兩種,一種是本地的,另一種是引用的。
還是以WD構(gòu)造函數(shù)為例,現(xiàn)在用prototype屬性進(jìn)行改寫:
function WD(skill){
this.skill = skill;
}
WD.prototype = { sex : '男' };
var WD1 = new WD('html');
var WD2 = new WD('css');
console.log(WD1.sex); // 男
console.log(WD2.sex); // 男
現(xiàn)在,sex屬性放在prototype對(duì)象里,是兩個(gè)實(shí)例對(duì)象共享的。只要修改了prototype對(duì)象,就會(huì)同時(shí)影響到兩個(gè)實(shí)例對(duì)象。
WD.prototype.sex = '女';
console.log(WD1.sex); //女
console.log(WD2.sex); // 女
由于所有的實(shí)例對(duì)象共享同一個(gè)prototype對(duì)象,那么從外界看起來(lái),prototype對(duì)象就好像是實(shí)例對(duì)象的原型,而實(shí)例對(duì)象則好像”繼承”了prototype對(duì)象一樣。這就是Javascript繼承機(jī)制的設(shè)計(jì)思想。