Javascript繼承機(jī)制的設(shè)計(jì)思想分享
它沒(méi)有"子類"和"父類"的概念,也沒(méi)有"類"(class)和"實(shí)例"(instance)的區(qū)分,全靠一種很奇特的"原型鏈"(prototype chain)模式,來(lái)實(shí)現(xiàn)繼承。
我花了很多時(shí)間,學(xué)習(xí)這個(gè)部分,還做了很多筆記。但是都屬于強(qiáng)行記憶,無(wú)法從根本上理解。
直到昨天,我讀到法國(guó)程序員Vjeux的解釋,才恍然大悟,完全明白了Javascript為什么這樣設(shè)計(jì)。
下面,我嘗試用自己的語(yǔ)言,來(lái)解釋它的設(shè)計(jì)思想。徹底說(shuō)明白prototype對(duì)象到底是怎么回事。其實(shí)根本就沒(méi)那么復(fù)雜,真相非常簡(jiǎn)單。
一、從古代說(shuō)起
要理解Javascript的設(shè)計(jì)思想,必須從它的誕生說(shuō)起。
1994年,網(wǎng)景公司(Netscape)發(fā)布了Navigator瀏覽器0.9版。這是歷史上第一個(gè)比較成熟的網(wǎng)絡(luò)瀏覽器,轟動(dòng)一時(shí)。但是,這個(gè)版本的瀏覽器只能用來(lái)瀏覽,不具備與訪問(wèn)者互動(dòng)的能力。比如,如果網(wǎng)頁(yè)上有一欄"用戶名"要求填寫(xiě),瀏覽器就無(wú)法判斷訪問(wèn)者是否真的填寫(xiě)了,只有讓服務(wù)器端判斷。如果沒(méi)有填寫(xiě),服務(wù)器端就返回錯(cuò)誤,要求用戶重新填寫(xiě),這太浪費(fèi)時(shí)間和服務(wù)器資源了。
因此,網(wǎng)景公司急需一種網(wǎng)頁(yè)腳本語(yǔ)言,使得瀏覽器可以與網(wǎng)頁(yè)互動(dòng)。工程師Brendan Eich負(fù)責(zé)開(kāi)發(fā)這種新語(yǔ)言。他覺(jué)得,沒(méi)必要設(shè)計(jì)得很復(fù)雜,這種語(yǔ)言只要能夠完成一些簡(jiǎn)單操作就夠了,比如判斷用戶有沒(méi)有填寫(xiě)表單。
1994年正是面向?qū)ο缶幊蹋╫bject-oriented programming)最興盛的時(shí)期,C++是當(dāng)時(shí)最流行的語(yǔ)言,而Java語(yǔ)言的1.0版即將于第二年推出,Sun公司正在大肆造勢(shì)。
Brendan Eich無(wú)疑受到了影響,Javascript里面所有的數(shù)據(jù)類型都是對(duì)象(object),這一點(diǎn)與Java非常相似。但是,他隨即就遇到了一個(gè)難題,到底要不要設(shè)計(jì)"繼承"機(jī)制呢?
二、Brendan Eich的選擇
如果真的是一種簡(jiǎn)易的腳本語(yǔ)言,其實(shí)不需要有"繼承"機(jī)制。但是,Javascript里面都是對(duì)象,必須有一種機(jī)制,將所有對(duì)象聯(lián)系起來(lái)。所以,Brendan Eich最后還是設(shè)計(jì)了"繼承"。
但是,他不打算引入"類"(class)的概念,因?yàn)橐坏┯辛?類",Javascript就是一種完整的面向?qū)ο缶幊陶Z(yǔ)言了,這好像有點(diǎn)太正式了,而且增加了初學(xué)者的入門(mén)難度。
他考慮到,C++和Java語(yǔ)言都使用new命令,生成實(shí)例。
C++的寫(xiě)法是:
ClassName *object = new ClassName(param);
Java的寫(xiě)法是:
Foo foo = new Foo();
因此,他就把new命令引入了Javascript,用來(lái)從原型對(duì)象生成一個(gè)實(shí)例對(duì)象。但是,Javascript沒(méi)有"類",怎么來(lái)表示原型對(duì)象呢?
這時(shí),他想到C++和Java使用new命令時(shí),都會(huì)調(diào)用"類"的構(gòu)造函數(shù)(constructor)。他就做了一個(gè)簡(jiǎn)化的設(shè)計(jì),在Javascript語(yǔ)言中,new命令后面跟的不是類,而是構(gòu)造函數(shù)。
舉例來(lái)說(shuō),現(xiàn)在有一個(gè)叫做DOG的構(gòu)造函數(shù),表示狗對(duì)象的原型。
function DOG(name){
this.name = name;
}
對(duì)這個(gè)構(gòu)造函數(shù)使用new,就會(huì)生成一個(gè)狗對(duì)象的實(shí)例。
var dogA = new DOG('大毛');
alert(dogA.name); // 大毛
注意構(gòu)造函數(shù)中的this關(guān)鍵字,它就代表了新創(chuàng)建的實(shí)例對(duì)象。
三、new運(yùn)算符的缺點(diǎn)
用構(gòu)造函數(shù)生成實(shí)例對(duì)象,有一個(gè)缺點(diǎn),那就是無(wú)法共享屬性和方法。
比如,在DOG對(duì)象的構(gòu)造函數(shù)中,設(shè)置一個(gè)實(shí)例對(duì)象的共有屬性species。
function DOG(name){
this.name = name;
this.species = '犬科';
}
然后,生成兩個(gè)實(shí)例對(duì)象:
var dogA = new DOG('大毛');
var dogB = new DOG('二毛');
這兩個(gè)對(duì)象的species屬性是獨(dú)立的,修改其中一個(gè),不會(huì)影響到另一個(gè)。
dogA.species = '貓科';
alert(dogB.species); // 顯示"犬科",不受dogA的影響
每一個(gè)實(shí)例對(duì)象,都有自己的屬性和方法的副本。這不僅無(wú)法做到數(shù)據(jù)共享,也是極大的資源浪費(fèi)。
四、prototype屬性的引入
考慮到這一點(diǎ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ì)象的屬性和方法,分成兩種,一種是本地的,另一種是引用的。
還是以DOG構(gòu)造函數(shù)為例,現(xiàn)在用prototype屬性進(jìn)行改寫(xiě):
function DOG(name){
this.name = name;
}
DOG.prototype = { species : '犬科' };
var dogA = new DOG('大毛');var dogB = new DOG('二毛');
alert(dogA.species); // 犬科alert(dogB.species); // 犬科
現(xiàn)在,species屬性放在prototype對(duì)象里,是兩個(gè)實(shí)例對(duì)象共享的。只要修改了prototype對(duì)象,就會(huì)同時(shí)影響到兩個(gè)實(shí)例對(duì)象。
DOG.prototype.species = '貓科';
alert(dogA.species); // 貓科
alert(dogB.species); // 貓科
五、總結(jié)
由于所有的實(shí)例對(duì)象共享同一個(gè)prototype對(duì)象,那么從外界看起來(lái),prototype對(duì)象就好像是實(shí)例對(duì)象的原型,而實(shí)例對(duì)象則好像"繼承"了prototype對(duì)象一樣。
這就是Javascript繼承機(jī)制的設(shè)計(jì)思想。不知道我說(shuō)清楚了沒(méi)有,繼承機(jī)制的具體應(yīng)用方法,可以參考我寫(xiě)的系列文章
- Javascript 繼承機(jī)制的實(shí)現(xiàn)
- javascript類繼承機(jī)制的原理分析
- 基于JavaScript實(shí)現(xiàn)繼承機(jī)制之原型鏈(prototype chaining)的詳解
- 由JavaScript中call()方法引發(fā)的對(duì)面向?qū)ο罄^承機(jī)制call的思考
- 基于JavaScript實(shí)現(xiàn)繼承機(jī)制之調(diào)用call()與apply()的方法詳解
- JavaScript 繼承機(jī)制的實(shí)現(xiàn)(待續(xù))
- 基于JavaScript實(shí)現(xiàn)繼承機(jī)制之構(gòu)造函數(shù)+原型鏈混合方式的使用詳解
- Javascript繼承機(jī)制詳解
相關(guān)文章
一文讀懂JS中的var/let/const和暫時(shí)性死區(qū)
這篇文章主要為大家詳細(xì)介紹了JavaScript中的var、let、const和暫時(shí)性死區(qū)的異同,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2023-02-02js實(shí)現(xiàn)點(diǎn)擊圖片自動(dòng)提交action的簡(jiǎn)單方法
下面小編就為大家?guī)?lái)一篇js實(shí)現(xiàn)點(diǎn)擊圖片自動(dòng)提交action的簡(jiǎn)單方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10設(shè)置下載不需要倒計(jì)時(shí)cookie(倒計(jì)時(shí)代碼)
利用賦值downvip實(shí)現(xiàn)軟件下載的倒計(jì)時(shí)代碼2008-11-11javascript同頁(yè)面多次調(diào)用彈出層具體實(shí)例代碼
一個(gè)在同一個(gè)頁(yè)面可多次調(diào)用的javascript彈出層效果,有需要的同學(xué)可以參考一下2013-08-08js內(nèi)置對(duì)象 學(xué)習(xí)筆記
今天系統(tǒng)的學(xué)了一下javascript的內(nèi)置對(duì)象。2011-08-08跨域請(qǐng)求兩種方法 jsonp和cors的實(shí)現(xiàn)
這篇文章主要介紹了跨域請(qǐng)求兩種方法 jsonp和cors的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11微信小程序基于高德地圖API實(shí)現(xiàn)天氣組件(動(dòng)態(tài)效果)
這篇文章主要介紹了微信小程序基于高德地圖API實(shí)現(xiàn)天氣組件(動(dòng)態(tài)效果),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-1020分鐘成功編寫(xiě)bootstrap響應(yīng)式頁(yè)面 就這么簡(jiǎn)單
這篇文章主要教大家如何在20分鐘內(nèi)成功編寫(xiě)bootstrap響應(yīng)式頁(yè)面,其實(shí)很簡(jiǎn)單,培養(yǎng)大家分分鐘開(kāi)發(fā)出一個(gè)高大上的頁(yè)面能力,感興趣的小伙伴們可以參考一下2016-05-05微信小程序?qū)崿F(xiàn)簡(jiǎn)單倒計(jì)時(shí)功能
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)簡(jiǎn)單倒計(jì)時(shí)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05基于javascript顯示當(dāng)前時(shí)間以及倒計(jì)時(shí)功能
這篇文章主要為大家詳細(xì)介紹了基于javascript顯示當(dāng)前時(shí)間以及倒計(jì)時(shí)功能的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03