新聞中心
1. 引言
繼承(inheritance)、封裝(encapsulation)和多態(tài)(polymorphism)是面向?qū)ο髾C(jī)制的主要特性。在JS中沒有“class”的概念,自然也無法直接進(jìn)行JAVA、C++常用到的extends、implements等操作。但從某種意義上來說,JS是純粹的“面向?qū)ο蟆本幊陶Z言,因?yàn)镴S中處處皆是對(duì)象(函數(shù)也是對(duì)象),而且作為函數(shù)式腳本語言,天生就是多態(tài)的。
網(wǎng)上很多文章探討JS中如何設(shè)計(jì)class和面向?qū)ο髾C(jī)制,這些文章的思路聚焦于如何嚴(yán)格按照J(rèn)AVA、C++中面向?qū)ο蟮膶?shí)現(xiàn)機(jī)制去在JS中實(shí)現(xiàn)同樣機(jī)制。但在我看來,既然JS中拋去了“class”的定義,就應(yīng)該充分享受JS的純粹對(duì)象機(jī)制帶來的便利。
2. 探索prototype
面向?qū)ο笾械腃lass是什么?其實(shí)本質(zhì)上就是一個(gè)“模板”,就像做月餅一樣,我們需要一個(gè)月餅?zāi)W?,而使用月餅?zāi)W幼龀龅脑嘛灮疽恢隆?br/>那么在JS中,如何定義“月餅?zāi)W印保?JS中提供了構(gòu)造函數(shù),構(gòu)造函數(shù)就是JS中的“月餅?zāi)W印?。考慮我廠生產(chǎn)月餅的如下代碼:
var 序列號(hào) = 0;
function 我廠月餅?zāi)W?廠名,日期){
this.序列號(hào) = 序列號(hào)++;
this.廠家名字 = 廠名;
this.生產(chǎn)日期 = 日期;
}
var 我廠月餅1 = new 我廠月餅?zāi)W?"我廠","20180806");
var 我廠月餅2 = new 我廠月餅?zāi)W?"我廠","20180807");
我們?cè)谏厦娴拇a中定義了“我廠月餅?zāi)W印边@個(gè)構(gòu)造函數(shù)(如果使用英文名請(qǐng)將首字母大寫),通過“new”操作做出了兩個(gè)月餅:月餅1和月餅2,還在月餅上打上了廠家名字和生產(chǎn)日期。除了沒有出現(xiàn)“class”字樣的關(guān)鍵字,這些代碼和JAVA、C++的代碼如此相似(需要注意,JS不支持函數(shù)重載,因此相同函數(shù)名的構(gòu)造函數(shù)無法被JS重載)。
我廠月餅生產(chǎn)線運(yùn)轉(zhuǎn)起來了。但好景不長,市場(chǎng)是殘酷的,市面上有許多月餅廠家,他們生產(chǎn)的月餅各有特色。我們發(fā)現(xiàn)某友商A廠提供未經(jīng)烘烤的月餅,可由消費(fèi)者買回家后自己進(jìn)行烘烤。我們買了一個(gè)A廠月餅,它是這樣定義的:
var A廠月餅1 = {
月餅形狀: "圓形",
生產(chǎn)日期: "20180706",
烘烤: function () {
console.log("提供烘烤功能");
}
}
JS中構(gòu)造函數(shù)具有prototype屬性,當(dāng)把構(gòu)造函數(shù)“我廠月餅?zāi)W印钡膒rototype屬性設(shè)置為A廠月餅1后,生產(chǎn)出來的“我廠月餅1”可直接引用prototype對(duì)象的屬性,如下代碼所示:
我廠月餅?zāi)W?prototype = A廠月餅1;
var 我廠月餅1 = new 我廠月餅?zāi)W?"我廠", "20180806");
我廠月餅1.烘烤();
現(xiàn)在我廠生產(chǎn)的月餅也有了烘烤功能了,并且具有形狀特征“圓形”,生產(chǎn)日期為“20180806”?!拔覐S月餅1”的對(duì)象屬性如圖1所示:
圖1可看到對(duì)象之間的原型鏈為:我廠月餅1->A廠月餅1->Object->null。
A廠生產(chǎn)的月餅形狀是可以變化的,可以做成“方形”,也可以做成“圓形”,經(jīng)過與A廠技術(shù)人員交流,我們得到了A廠生產(chǎn)月餅的構(gòu)造函數(shù)如下:
function A廠月餅?zāi)W?形狀, 日期) {
this.月餅形狀 = 形狀;
this.生產(chǎn)日期 = 日期;
this.烘烤 = function () {
console.log("提供烘烤功能");
}
}
很明顯,A廠使用這個(gè)月餅?zāi)W涌梢宰龀龆喾N形狀的月餅,按照以前的方法,我們使用一個(gè)A廠生產(chǎn)的月餅作為“我廠月餅?zāi)W印钡膒rototype,只能固定一個(gè)形狀,現(xiàn)在我們也希望在使用“我廠月餅?zāi)W印睍r(shí),可以做出不同形狀的月餅。怎么辦呢?辦法就是在“我廠月餅?zāi)W印敝幸谩癆廠月餅?zāi)W印?,參考如下代碼:
function 我廠月餅?zāi)W?形狀,廠名, 日期) {
A廠月餅?zāi)W?call(this,形狀);
this.序列號(hào) = 序列號(hào)++;
this.廠家名字 = 廠名;
this.生產(chǎn)日期 = 日期;
}
var 我廠月餅1 = new 我廠月餅?zāi)W?"方形","我廠", "20180806");
再次查看“我廠月餅1”對(duì)象,發(fā)現(xiàn)已有“方形”這個(gè)屬性了。如圖2所示。
與圖1所不同的是,“我廠月餅1”對(duì)象的原型鏈已經(jīng)發(fā)生了變化,因?yàn)檫@次,我們沒有使用“我廠月餅?zāi)W印钡膒rototype。
到此為止,似乎一切都已經(jīng)塵埃落定,我廠不僅保留了原來的月餅特色,還包含了A廠的月餅特色,一切似乎都是那么的美好。但是不久,我們發(fā)現(xiàn)--又出狀況了。A廠生產(chǎn)的月餅提供了DIY配色的功能,用戶能夠根據(jù)月餅提供的配色包對(duì)月餅進(jìn)行配色,這一功能頗受部分特定人群的歡迎。
聯(lián)系A(chǔ)廠技術(shù)人員,發(fā)現(xiàn)他們對(duì)“A廠月餅?zāi)W印弊隽诵薷?,在prototype里增加了配色函數(shù),代碼如下:
A廠月餅?zāi)W?prototype.配色 = function(){
console.log("提供配色功能");
}
如果我們想繼續(xù)共享“A廠月餅?zāi)W印钡摹芭渖惫δ?,還是得從prototype來想辦法,這次我們?cè)谠瓉淼拇a上將“A廠月餅?zāi)W印钡膒rototype設(shè)置為一個(gè)通用的“A廠月餅?zāi)W印鄙傻摹癆廠月餅”(構(gòu)造函數(shù)調(diào)用時(shí)不帶參數(shù)),完整代碼如下:
function A廠月餅?zāi)W?形狀, 日期) {
this.月餅形狀 = 形狀;
this.生產(chǎn)日期 = 日期;
this.烘烤 = function () {
console.log("提供烘烤功能");
}
}
A廠月餅?zāi)W?prototype.配色 = function(){
console.log("提供配色功能");
}
var 序列號(hào) = 0;
function 我廠月餅?zāi)W?形狀,廠名, 日期) {
A廠月餅?zāi)W?call(this,形狀);
this.序列號(hào) = 序列號(hào)++;
this.廠家名字 = 廠名;
this.生產(chǎn)日期 = 日期;
}
我廠月餅?zāi)W?prototype = new A廠月餅?zāi)W?);
var 我廠月餅1 = new 我廠月餅?zāi)W?"方形","我廠", "20180806");
再次查看“我廠月餅1”對(duì)象,如圖3所示,已經(jīng)具有配色的功能(請(qǐng)注意原型鏈已有變化)。
到了現(xiàn)在,終于可以噓一口氣了,A廠再在prototype中增加新功能,我們的代碼不用改了。
3. 使用prototype
在上節(jié)月餅?zāi)W拥睦又?,我們探索了prototype,那么prototype是一個(gè)怎樣的存在,我們來總結(jié)一下:
1. prototype專屬于構(gòu)造函數(shù),在使用構(gòu)造函數(shù)new出來的對(duì)象中,使用__proto__表示。
2. prototype對(duì)象中包含的屬性(包括函數(shù)屬性)被使用構(gòu)造函數(shù)構(gòu)建的對(duì)象所共享。從某種意義上來說,prototype對(duì)象就是父對(duì)象。
下面我們根據(jù)上節(jié)的示例歸納一下不同應(yīng)用場(chǎng)景下如何使用prototype。為便于描述,我們將需要共享其它對(duì)象屬性的對(duì)象稱為子對(duì)象,生成子對(duì)象所使用的構(gòu)造函數(shù)稱為子構(gòu)造函數(shù),提供共享屬性的對(duì)象稱為父對(duì)象,生成父對(duì)象使用的構(gòu)造函數(shù)稱為父構(gòu)造函數(shù)。
我們歸納出如下規(guī)則:
1. 若子對(duì)象只想共享父構(gòu)造函數(shù)中定義的屬性,在子構(gòu)造函數(shù)調(diào)用父構(gòu)造函數(shù)即可,需要注意的是子構(gòu)造函數(shù)的參數(shù)可能需要調(diào)整。
2. 若子對(duì)象想共享父構(gòu)造函數(shù)和prototype中的所有屬性:當(dāng)父構(gòu)造函數(shù)無參數(shù)時(shí),只需要賦值子構(gòu)造函數(shù)的prototype為使用父構(gòu)造函數(shù)new出來的一個(gè)父對(duì)象即可;當(dāng)父構(gòu)造函數(shù)有參數(shù)時(shí),不僅要賦值子構(gòu)造函數(shù)的prototype為使用父構(gòu)造函數(shù)new出來的一個(gè)父對(duì)象(構(gòu)造函數(shù)不帶參數(shù)),還需要在子構(gòu)造函數(shù)調(diào)用父構(gòu)造函數(shù)(初始化父構(gòu)造函數(shù)中的參數(shù))。
當(dāng)我們使用DDD(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì))思想來設(shè)計(jì)軟件時(shí),在建模時(shí)我們會(huì)設(shè)計(jì)領(lǐng)域中的實(shí)體、值對(duì)象和聚合。
領(lǐng)域中數(shù)量最多的應(yīng)該是實(shí)體,這些實(shí)體也即編程語言中的對(duì)象。設(shè)想我們使用JS來編程領(lǐng)域模型,當(dāng)我們使用“對(duì)象共享屬性”的觀點(diǎn)來看待原來的“對(duì)象繼承”關(guān)系時(shí),也能實(shí)現(xiàn)使用JAVA、C++等編程語言達(dá)到的效能。
4. 小結(jié)
prototype是JS中常令人迷惑的一個(gè)概念,之所以令人迷惑是因?yàn)榇蠹铱偸窍氚阉c面向?qū)ο蟮慕?jīng)典框架結(jié)合起來,反而束縛了自己的思維。JS是一個(gè)純粹的面向?qū)ο笙到y(tǒng),使用構(gòu)造函數(shù)的prototype實(shí)現(xiàn)了對(duì)象屬性間的共享,本文探索了prototype的本質(zhì)并歸納總結(jié)了prototype的使用規(guī)則。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。
當(dāng)前標(biāo)題:漫談JS中的prototype-創(chuàng)新互聯(lián)
鏈接分享:http://fisionsoft.com.cn/article/cepjoi.html