相關(guān)知識(shí)庫:
Java SE知識(shí)庫
Java 知識(shí)庫
Java EE知識(shí)庫
JavaScript知識(shí)庫
jQuery知識(shí)庫
AngularJS知識(shí)庫
主題一、原型 一、基于原型的語言的特點(diǎn)
1 只有對象,沒有類;對象繼承對象,而不是類繼承類。
2 “原型對象”是基于原型語言的核心概念。原型對象是新對象的模板,它將自身的屬性共享給新對象。一個(gè)對象不但可以享有自己創(chuàng)建時(shí)和運(yùn)行時(shí)定義的屬性,而且可以享有原型對象的屬性。
3 除了語言原生的頂級(jí)對象,每一個(gè)對象都有自己的原型對象,所有對象構(gòu)成一個(gè)樹狀的層級(jí)系統(tǒng)。root節(jié)點(diǎn)的頂層對象是一個(gè)語言原生的對象,其他所有對象都直接或間接繼承它的屬性。
顯然,基于原型的語言比基于類的語言簡單得多,我們只需要知道"用對象去創(chuàng)建對象",就可以在原型的世界里大行其道了!
二、基于原型的語言中對象的創(chuàng)建
創(chuàng)建有兩個(gè)步驟
1. 使用"原型對象"作為"模板"生成新對象
這個(gè)步驟是必要的,這是每個(gè)對象出生的唯一方式。以原型為模板創(chuàng)建對象,這也是"原型"(prototype)的原意。
2. 初始化內(nèi)部屬性
這一步驟不是必要的。通俗點(diǎn)說,就是,對"復(fù)制品"不滿意,我們可以"再加工",使之獲得不同于"模板"的"個(gè)性"。
這兩個(gè)步驟很自然,也很好理解,比使用類構(gòu)造對象從概念上簡單得多了。對于習(xí)慣了java基于類的面向?qū)ο蟮恼Z言的程序員, 這種"新穎"的生成對象的方式一定會(huì)讓他們感到好奇。
三、原型,為復(fù)用代碼而生
使用原型,能復(fù)用代碼,節(jié)省內(nèi)存空間
舉個(gè)例子,存在舊對象oldObject,它有一個(gè)屬性name,值是’Andy’, 和一個(gè)名為getName()的方法,如果以該對象為原型創(chuàng)建一個(gè)新對象,
Js代碼
那么新對象newObject同樣具有屬性name,值也是’Andy’,也有一個(gè)方法getName()。值得注意的是,newObject并不是在內(nèi)存中克隆了oldObject,它只是引用了oldObject的屬性, 導(dǎo)致實(shí)際的效果好像"復(fù)制"了newObject一樣。
newObject = create(oldObject);創(chuàng)建的對象newObject只有一個(gè)屬性,這個(gè)屬性的值是原型對象的地址(或者引用),如下圖所示。
當(dāng)對象訪問屬性的時(shí)候,如果在內(nèi)部找不到,那么會(huì)在原型對象中查找到屬性;如果原型對象中仍然找不到屬性,原型對象會(huì)查找自身的原型對象,如此循環(huán)下去,直至找到屬性或者到達(dá)頂級(jí)對象。對象查找屬性的過程所經(jīng)過的對象構(gòu)成一條鏈條,稱之為原型鏈。newObject,oldObject和topObject就構(gòu)成一條原型鏈。
下面列出newObject的3種的查找屬性情況
newObject查找name,
1 內(nèi)部找不到,到原型對象中查找
2 oldObject中查找到了name,成功返回;
newObject查找toString
1 內(nèi)部找不到,到原型對象中查找
2 oldObject中查找不到toString,到原型對象中查找
3 topObject中查找到了toString,成功返回;
newObject查找valueOf
1 內(nèi)部找不到,到原型對象中查找
2 oldObject中查找不到valueOf,到原型對象中查找
3 topObject中還是找不到,而且topObject是頂層對象,所以返回錯(cuò)誤或者空值。
對象會(huì)通過原型鏈動(dòng)態(tài)地查找屬性,對象的所擁有的屬性并不是靜態(tài)的。如果原型鏈上的一個(gè)對象發(fā)生的改變,那么這個(gè)改變也會(huì)馬上會(huì)反應(yīng)到在原型鏈中處于該對象下方的所有對象。
三、繼承
如果以oldObject為原型創(chuàng)建了newObject,那么可以說newObject繼承了oldObject。
在java中 通過語句class Cat extends Animal定義Cat類繼承Animal類,Cat類產(chǎn)生的實(shí)例對象便擁有了Animal類中定義的屬性。類似地,在基于原型的語言中, 通過cat = create(animal)創(chuàng)建以animal對象為模板的cat對象,cat對象便擁有了animal對象中的屬性,因此可以說cat對象繼承了anmial對象。
四、小結(jié)
原型的本質(zhì)就是對象引用原型對象的屬性,實(shí)現(xiàn)代碼復(fù)用。
基于原型的語言是以原型對象為模板創(chuàng)建對象newObject = create(oldObject)。
主題二、深刻理解JavaScript基于原型的面向?qū)ο?/span>
一、飽受爭議的javascript
javascript本質(zhì)上是基于原型的語言,但是卻引入了基于類的語言的new關(guān)鍵字和constructor模式,導(dǎo)致javascript飽受爭議。
javascript的作者Brendan Eich 1994年研發(fā)這門語言的時(shí)候,C++語言是最流行的語言,java1.0即將發(fā)布,面向?qū)ο缶幊虅莶豢蓳?,于是他認(rèn)為,引入new關(guān)鍵字可以使習(xí)慣C++/java程序員更容易接受和使用javascript。
實(shí)際上,事實(shí)證明引入new是個(gè)錯(cuò)誤的決定。
C++/java程序員看到new一個(gè) function的時(shí)候,他們會(huì)認(rèn)為js通過function創(chuàng)建對象,function相當(dāng)于類,接著他們會(huì)嘗試在js挖掘類似java/C++面向類的編程特性,結(jié)果他們發(fā)現(xiàn)function沒有extends,反而有個(gè)很奇怪的prototype對象,于是他們開始咒罵,js的面向?qū)ο筇愀饬恕4_實(shí),new的引入讓他們以為js的面向?qū)ο笈cjava/C++類似,實(shí)際上并不是,如果不是以原型本質(zhì)去理解js的面向?qū)ο螅⒍ㄒ馐艽煺?,new,prototype,__proto__都是javascript實(shí)現(xiàn)原型的具體手段。
另一方面,理解原型的程序員,也表示不高興,因?yàn)榫尤灰褂胣ew function的語法來間接實(shí)現(xiàn)原型繼承,三行代碼才做到最基本的原型繼承,下面是實(shí)現(xiàn)對象newObject繼承對象oldObject的代碼,
Js代碼
這太繁瑣了?;谠驼Z言理論上應(yīng)該存在一個(gè)函數(shù)create(prototypeObject),功能是基于原型對象產(chǎn)生新對象,例如,
var newObject = create(oldObject);
看到這樣的代碼,人們就會(huì)自然很清晰地聯(lián)想到,newObject是以oldObject模板構(gòu)造出來的。
js是世界上最容易被誤解的語言,原因主要有兩個(gè):
1) 作為基于原型的語言中,卻連最基本的一個(gè)通過原型產(chǎn)生對象的函數(shù)create(prototypeObject)也沒有,讓人不知道js根本上是以對象創(chuàng)建對象。應(yīng)該添加該函數(shù),現(xiàn)在Chrome和IE9的Object對象就有這個(gè)create函數(shù)。
2) 使用new func形式創(chuàng)建對象,讓人誤會(huì)js是以類似java類的構(gòu)造函數(shù)創(chuàng)建對象,實(shí)際上,構(gòu)造函數(shù)根本上在創(chuàng)建對象上起到次要的作用,甚至不需要,重要的只有函數(shù)的屬性prototype引用的原型對象,新對象以此為模板生成,生成之后才調(diào)用函數(shù)做初始化的操作,而初始化操作不是必要的。應(yīng)該把廢棄new 操作符,把new func分解為兩步操作,
var newObject = create(func.prototype);
func.call(newObject);
這樣程序員才好理解。如果想把這兩個(gè)步驟合二為一,應(yīng)該使用new以外的關(guān)鍵字。
到這里,我們務(wù)必要牢牢印入腦海的是,js的面向?qū)ο笫腔谠偷拿嫦驅(qū)ο螅瑢ο髣?chuàng)建的方式根本上只有一種,就是以原型對象為模板創(chuàng)建對象,newObject = create(oldObject)。new function不是通過函數(shù)創(chuàng)建對象,只是刻意模仿java的表象。
js在面向?qū)ο笊显庥龅臓幾h,完全是因?yàn)樯虡I(yè)因素導(dǎo)致作者失去了自己的立場。就像現(xiàn)在什么產(chǎn)品都加個(gè)云一樣,如果那時(shí)候不加個(gè)new關(guān)鍵字來標(biāo)榜自己面向?qū)ο?,產(chǎn)生"js其實(shí)類似c++/java"的煙幕,可能根本沒有人去關(guān)注javascript。更令人啼笑皆非的是,原本稱作LiveScript的javascript,因?yàn)?后期和SUN合作,并且為了沾上當(dāng)時(shí)被SUN炒得火熱的Java的光,發(fā)布的時(shí)候居然改名成Javascript。
二、從原型本質(zhì),站在語言設(shè)計(jì)者角度,理解constructor模式
假想我們是當(dāng)時(shí)設(shè)計(jì)javascript繼承機(jī)制的Brendan Eich,我們會(huì)怎么設(shè)計(jì)js的面向?qū)ο竽兀?/div>
現(xiàn)在javascript開發(fā)到這樣的階段
1) 擁有基本類型,分支和循環(huán),基本的數(shù)學(xué)運(yùn)算,
2) 所有數(shù)據(jù)都是對象
3) 擁有類似C語言的function
4) 可以用var obj = {}語句生成一個(gè)空對象,然后使用obj.xxx或obj[xxx]設(shè)置對象屬性
5) 沒有繼承,沒有this關(guān)鍵字,沒有new
我們?nèi)蝿?wù)是,實(shí)現(xiàn)javascript的面向?qū)ο?,最好能達(dá)到類似java的創(chuàng)建對象和繼承效果。更具體一點(diǎn),我們要擴(kuò)充js語言,實(shí)現(xiàn)類似下面的java代碼。
Java代碼
1 實(shí)現(xiàn)創(chuàng)建對象
現(xiàn)有的對象都是基本類型,怎么創(chuàng)建用戶自定義的對象呢?
(解釋:
var i = 1;
這里的i是解釋器幫忙封裝的Number對象,雖然看起來跟C的int沒區(qū)別,但實(shí)際上可以i.toString()。
)
java使用構(gòu)造函數(shù)來產(chǎn)生對象,我們嘗試把java的Empolyee的構(gòu)造函數(shù)代碼拷貝下來,看看可不可以模仿
Js代碼
我們只要生成一個(gè)空對象obj,再把函數(shù)里面的this換成obj,執(zhí)行函數(shù),就可以生成自定義對象啦!我們把Employee這樣用來創(chuàng)建對象的函數(shù)稱作構(gòu)造函數(shù)。
1) 首先我們用原生的方式為function添加方法call和apply,實(shí)現(xiàn)把把函數(shù)里面的this替換成obj。call,apply在Lisp語言中已經(jīng)有實(shí)現(xiàn),很好參考和實(shí)現(xiàn)。
2) 然后實(shí)現(xiàn)生成實(shí)例
Js代碼
3) 到這里,以類似java方式產(chǎn)生對象基本完成了,但是這個(gè)employee對象沒有方法
我們的function是第一類對象,可以運(yùn)行時(shí)創(chuàng)建,可以當(dāng)做變量賦值,所以沒有問題
Js代碼
2 實(shí)現(xiàn)繼承
創(chuàng)建對象成功了,接著考慮實(shí)現(xiàn)繼承?,F(xiàn)在我們所有數(shù)據(jù)都是對象,沒有類,有兩種方案擺在我們的面前
a.類繼承
b.原型繼承
2.a實(shí)現(xiàn)類繼承
a方案是首選方案,因?yàn)楦鷍ava相似的話,JS更容易被接受
先粘貼Java構(gòu)造函數(shù)的代碼
Js代碼
1) 把extends后面的函數(shù)自動(dòng)記錄下來,放到function對象的parentFunc變量
2) 如果第一行是super(),替換成var parent = newInstance(Coder.parentFunc,XXX),這樣內(nèi)部保留一個(gè)名為parent父對象;
3) 把this替換為obj,super替換換成parent
4) "."和"[]"重新定義,需要支持在對象內(nèi)部parent對象查找屬性。
這四步都屬于比較大的改動(dòng),只要認(rèn)真想一想都覺得不是太容易。
更重要的是,即使把這4步實(shí)現(xiàn)了,不但語言變得太復(fù)雜了,而且產(chǎn)生的對象根本享受不了繼承帶來的好處——內(nèi)存中的代碼復(fù)用,因?yàn)檫@樣產(chǎn)生的每個(gè)對象都有"父類(函數(shù))"的代碼而不是僅有一份。這時(shí)候該注意到j(luò)ava中使用類的意義了,java類的代碼在內(nèi)存只有一份,然后每個(gè)對象執(zhí)行方法都是引用類的代碼,所有子類對象調(diào)用父類方法的時(shí)候,執(zhí)行的代碼都是同一份父類的方法代碼。但是JS沒有類,屬性和方法都是存在對象之中,根本沒有辦法做到j(luò)ava那樣通過類把代碼共享給所有對象!
a方案宣告失敗
2.b 實(shí)現(xiàn)原型繼承
看b方案。我們現(xiàn)在的js語言,一切都是對象,顯然非常適合使用基于原型的繼承方式,就看具體如何實(shí)現(xiàn)了。
我們新建一個(gè)topObject來代表頂層對象,那么創(chuàng)建employee對象的時(shí)候,應(yīng)該在employee對象內(nèi)部設(shè)置一個(gè)屬性引用topObject;同理,創(chuàng)建coder對象的時(shí)候,應(yīng)該在coder對象內(nèi)部設(shè)置一個(gè)屬性引用employee對象,我們把這個(gè)引用原型對象的屬性命名約定為"__proto__"。更進(jìn)一步,為了構(gòu)建一個(gè)對象的過程更自然,構(gòu)建時(shí)候應(yīng)該先在新對象中設(shè)置引用原型對象的屬性,以表示先用模板制作出一個(gè)和模板一致的對象,然后再才執(zhí)行構(gòu)造函數(shù)初始化這個(gè)新對象自身的屬性,以添加個(gè)性化的東西。具體實(shí)現(xiàn)代碼如下:
Js代碼
當(dāng)然我們還要做的工作就是在javascript解釋器中增加對__proto__的支持,當(dāng)一個(gè)對象訪問一個(gè)自身沒有的屬性的時(shí)候,就通過__proto__屬性查找原型鏈上是否存在該屬性。
優(yōu)化1. 函數(shù)封裝
這一切看起來并不是那么美好,我們創(chuàng)建一個(gè)employee對象需要3行代碼,我們需要這么一個(gè)函數(shù)封裝這3行代碼
function newInstance(prototype,constructor,arg1,arg2,....);
//第一個(gè)參數(shù)是原型對象,第二個(gè)是構(gòu)造函數(shù),后面的是構(gòu)造函數(shù)的參數(shù)
可以這么實(shí)現(xiàn)
Js代碼
優(yōu)化2. 縮減參數(shù)
仔細(xì)一看,function newInstance的參數(shù)可以更少,我們可以把原型對象prototype作為屬性放在constructor,那樣我們的函數(shù)就可以只有一個(gè)參數(shù)了。屬性名就約定為prototype吧。
2.1 我們修改解釋器,把topObject寫入語言作為原生的頂級(jí)對象;再修改function的源代碼,讓每一個(gè)新建的function都默認(rèn)具有屬性prototype = topObject
2.2 優(yōu)化后的代碼如下
Js代碼
到達(dá)這一步,可以發(fā)現(xiàn),我們的最終實(shí)現(xiàn)和Breandan Eich非常類似,在期待盡量模仿java創(chuàng)建對象的前提下,Brendan Eich 當(dāng)時(shí)的設(shè)計(jì)是合乎情理的,是良好的。他相對于我們方案的唯一不同就是他使用了new關(guān)鍵字,而我們使用了newInstance函數(shù)。
盡管new關(guān)鍵字容易讓人誤解,但是背后偉大的思想,決定了時(shí)至今日,javascript依然是瀏覽器編程語言的龍頭大哥,甚至發(fā)展到復(fù)雜的node.js服務(wù)端編程。
三、從javascript的原型本質(zhì),理解javascript的構(gòu)造器模式
在"從原型本質(zhì),站在語言設(shè)計(jì)者角度,理解constructor模式"一節(jié)中我們站在設(shè)計(jì)者角度粗略重現(xiàn)了js的設(shè)計(jì)過程?,F(xiàn)在我們換個(gè)角色,不是語言設(shè)計(jì)者,而是熟悉原型概念并且知道js是基于原型的語言的程序員,去理解js的使用(new關(guān)鍵字+函數(shù))的創(chuàng)建對象方式。
1. 理解new func()
Js代碼
分析上面代碼。
javascript引入new關(guān)鍵字是為了模仿java創(chuàng)建對象的方式,通過語句var employee = new Employee('Jack') 就生成了一個(gè)employee對象。
我們知道,基于原型的語言生成一個(gè)步驟有兩步,第一步是使用"原型對象"作為"模板"生成新對象,第二步是初始化新對象的內(nèi)部屬性。
我們敢肯定地推斷,javascript中的new Employee('Jack');必然做了這兩件事情,那么
1 "原型對象"在哪里?
2 怎么做到"初始化新對象的內(nèi)部屬性"?
答案是,Employee.prototype就是我們要找的"原型對象",通過"以新對象代替this,執(zhí)行Employee函數(shù)"做到了"初始化新對象的內(nèi)部屬性"。
使用new+function的方式創(chuàng)建對象,其實(shí)就是應(yīng)用我們設(shè)計(jì)的函數(shù)newInstance時(shí)的思想
Js代碼
javascript把生成一個(gè)對象所需的兩個(gè)元素——"原型對象"和"初始化"都集中在構(gòu)造函數(shù),以簡化創(chuàng)建對象的過程,其實(shí)是個(gè)良好的設(shè)計(jì)。唯一的缺點(diǎn)是new關(guān)鍵字容易讓人誤會(huì)。
2. 簡單羅列javascript構(gòu)造器模式的特點(diǎn)
1) javascript的頂層對象是Object.prototype
2) 所有對象有一個(gè)__proto__屬性。__proto__指向自己的"原型對象",搜索屬性的原型鏈以__proto__為基礎(chǔ)。
3) 每個(gè)函數(shù)都會(huì)默認(rèn)關(guān)聯(lián)一個(gè)原型對象。javascript每創(chuàng)建一個(gè)函數(shù)的時(shí)候,都同時(shí)創(chuàng)建一個(gè)原型對象,賦值到函數(shù)的prototype屬性,用作使用new 生成實(shí)例對象的默認(rèn)原型對象。該默認(rèn)原型對象的內(nèi)容是
Js代碼
__proto__指向Object.prototype的目的是為了使生成的實(shí)例對象繼承頂層對象Object.prototype;
而constructor指向函數(shù)本身的目的是為了使生成的實(shí)例對象newObject可以直接通過newObject.constructor訪問到構(gòu)造函數(shù),同時(shí)構(gòu)造函數(shù)和原型對象可以互相訪問也是個(gè)良好的設(shè)計(jì)。但是,實(shí)際上,constructor并沒有什么用,所以大家可以不理會(huì)這個(gè)屬性,這僅僅是一個(gè)優(yōu)化的設(shè)計(jì)。
構(gòu)造函數(shù),原型對象,實(shí)例對象的三角關(guān)系圖如下
4) 可以修改或替換構(gòu)造函數(shù)都會(huì)默認(rèn)關(guān)聯(lián)的原型對象。需要注意的的是,不少資料說,如果是使用自定義的對象替換了構(gòu)造函數(shù)f默認(rèn)關(guān)聯(lián)的原型對象,最好添加一行代碼
Js代碼
以確保維護(hù)正確的三角關(guān)系。
例如
Js代碼
但是經(jīng)過我的測試,即使不寫上一行Coder.prototype.constructor = Coder;,以下測試都表現(xiàn)正確
Js代碼
也就是說原型對象的construtctor屬性根本不影響繼承,它只是普通的一個(gè)附加屬性,沒有任何特殊作用,我們可以完全無視這個(gè)屬性。
不寫上一行Coder.prototype.constructor = Coder;,唯一會(huì)引起的錯(cuò)誤只有,coder.constructor的結(jié)果是Employee,而不是Coder。實(shí)
際上我們并不會(huì)關(guān)心coder.constructor,我們關(guān)心的只是是繼承,所以即使不寫上一行Coder.prototype.constructor = Coder;也沒有關(guān)系。
5) 以下代碼幾乎涵蓋了上面所討論的特點(diǎn),建議讀者在chrome中運(yùn)行該代碼以加深對構(gòu)造器模式的理解
5.a.代碼
Php代碼
5.b.對象繼承體系結(jié)構(gòu)圖
下圖是上面5.a代碼的對象整體結(jié)構(gòu)圖(圖片較大,可以下載到本地縮小來看)
從整體上看,這像極了java的類繼承體系結(jié)構(gòu),實(shí)際上這就是js的對象繼承體系結(jié)構(gòu)。
里面的對象有三種角色,紫色的是構(gòu)造函數(shù),黃色的是原型對象,綠色的是實(shí)例對象,當(dāng)然不能嚴(yán)格區(qū)分這些角色,例如匿名的Employee實(shí)例對象充當(dāng)了Coder的原型對象。
紫色的構(gòu)造函數(shù)和原型對象之間有一個(gè)雙向箭頭,這個(gè)雙向箭頭的意思,構(gòu)造函數(shù)有一個(gè)prototype屬性指向原型對象,而原型隊(duì)形也有一個(gè)constructor屬性指向構(gòu)造函數(shù),它們之間有著互相引用的關(guān)系。
單線箭頭,表示的是對象繼承關(guān)系。
從這個(gè)圖,我們可以直觀地看到
1) 所有對象都有自己的原型對象。所有構(gòu)造函數(shù)的原型對象都是Function.prototype,Object.prototype是最頂層的對象。我們可以在Function.prototype上增加方法,那么在原型鏈下方的函數(shù),就可獲得這些方法,同理我們可以在Object.prototype上增加方法,那么js所有對象都擁有了這個(gè)方法。
2) 通過原型繼承,所有對象構(gòu)成了一個(gè)完整的系統(tǒng)
3) 我相信你能夠發(fā)現(xiàn)更多有趣的的東西.如果你覺得這篇文章不值得一看,那么請至少看看這張圖片,結(jié)合這張圖片重新思考下js原型的理念,應(yīng)該能給你一些有益的回報(bào)。
6. 構(gòu)造器模式的best practice
1) 方法最好放在原型對象中,讓每個(gè)實(shí)例對象都共享同一個(gè)方法。如果方法放在構(gòu)造函數(shù)中,那么每個(gè)對象都有自己獨(dú)立的一份方法代碼,浪費(fèi)內(nèi)存。
2) 字段變量(fields,variables)最好放在構(gòu)造函數(shù)中,讓每個(gè)實(shí)例對象都具有一份自己的字段。除非要在所有子類中共享,實(shí)現(xiàn)類似靜態(tài)變量的效果,才把字段放在原型中。
3) 繼承層次不宜過深,原型鏈查找會(huì)耗費(fèi)時(shí)間。
例如,
上面第5)點(diǎn)中的代碼片段中,
1)Employee和Coder的方法都放在了原型中
2)Coder產(chǎn)生的實(shí)例對象雖然繼承自匿名employee對象(new Employee('')),擁有name屬性,但是為了每個(gè)Coder產(chǎn)生的實(shí)例對象都擁有屬于自己的一份name屬性,我們選擇在構(gòu)造函數(shù)中重復(fù)定義name屬性,覆蓋匿名employee對象的name屬性。
四、模擬基于類的面向?qū)ο?/strong>
1. 該不該模擬類
javascript是基于原型的語言,具有強(qiáng)大的表達(dá)能力,足可以模擬基于類的面向?qū)ο?。相信大家也看過不少模擬類的js代碼,這里不打算羅列。
但是,js畢竟是原型繼承的語言,應(yīng)該要按照原型繼承的思維去表達(dá)面向?qū)ο?,而不是用類的思維,這樣才能表現(xiàn)出js的真正的威力。
如果要模擬的話,模擬一些最基本的操作就可以,不要嘗試深入模擬基于類的語言的復(fù)雜特性,否則會(huì)犯下跟Brendan Eich同樣的錯(cuò)誤。模擬的出發(fā)點(diǎn)是方便程序員能夠更容易地使用js面向?qū)ο?,但是理解了?gòu)造器模式和原型鏈的前提下,沒有模擬的必要,只需要封裝一些常用的操作就OK了。
js的對象沒有類型,根本不需要像java的對象那樣需要關(guān)心自己的類繼承體系以檢查類型轉(zhuǎn)換是否正確,所以模仿類繼承沒有意義。在js中只需要關(guān)心對象的內(nèi)容,關(guān)心對象能否繼承其他對象的屬性就足夠了。
我就曾經(jīng)是一個(gè)被誤導(dǎo)的程序員??磩e人寫的面向?qū)ο蠼坛?,以為js需要我們開發(fā)一些函數(shù),才能使用面向?qū)ο?。我用過prototype.js 的Class.create,那時(shí)候我的感覺很不爽,我抱怨js為什么連最基本的class都沒有。
如果讓我寫一篇文章,介紹js的面向?qū)ο?,我?huì)先教會(huì)讀者領(lǐng)會(huì)這個(gè)函數(shù),
Js代碼
我要讓讀者知道,js是基于原型的語言,它用只能以對象為模板創(chuàng)建對象,它用對象繼承對象。它沒有類,也不需要類,一切都是對象。在這之后再介紹如何模擬class就無所謂了,因?yàn)槔斫饬薺avascript的原型本質(zhì)之后,就會(huì)知道模擬類的實(shí)質(zhì)是還是調(diào)用原型的特性,也就不會(huì)過分期待js能夠像java一樣操作類和對象,而且能夠發(fā)現(xiàn)原型的面向?qū)ο竽軌驇韨鹘y(tǒng)面向?qū)ο笳Z言無法比擬強(qiáng)大特性。
2. 欣賞Crockford對類的模擬
拜讀了Crockford的一些文章和他寫的JavaScript:The Good Parts,覺得他寫的一些對js的簡單封裝很有意思,也很實(shí)用。
下面是我對他封裝js面向?qū)ο蟮睦斫夂涂偨Y(jié),希望對讀者有用。
創(chuàng)建對象的方法根本上只有一種方式:以原型對象為模板創(chuàng)建對象,但是在形式上可以多種多樣。
在JS中,從形式上,除去字面量方式創(chuàng)建對象之外,有三種常單創(chuàng)建對象的形式(or you can call it a "pattern",anyway)
1) 使用構(gòu)造函數(shù)創(chuàng)建對象(constructor pattern)
Crockford通過函數(shù)Function.prototype.method和Function.prototype.inherits方法"美化"了傳統(tǒng)構(gòu)造模式創(chuàng)建對象的代碼。
值得注意的是,method方法和inherits方法的封裝把prototype從代碼中除去,掩蓋了原型本質(zhì),需要程序理解構(gòu)造器模式的前提下才可使用。
Js代碼
增強(qiáng)1.模擬私有變量。
上面構(gòu)造函數(shù)所產(chǎn)生的對象只有public成員,沒有private成員,可以通過閉包實(shí)現(xiàn)私有成員
Js代碼
私有成員帶來的代價(jià)是,訪問私有變量的方法不能放置在原型對象中被共享,導(dǎo)致每個(gè)生成的對象在內(nèi)存都獨(dú)立擁有一份訪問私有變量方法的代碼。
增前2. 模擬super.method.
構(gòu)造函數(shù)coder能不能模仿出java中類似super.method的效果呢?答案是可以的,最簡單的實(shí)現(xiàn)是在對象內(nèi)部創(chuàng)建一個(gè)副對象的副本。缺點(diǎn)是增加了內(nèi)存的消耗。
Js代碼
2) 使用原型創(chuàng)建對象(prototypal pattern)
這種創(chuàng)建方式直白地顯示了原型語言創(chuàng)建對象的特點(diǎn)
Js代碼
3) 使用函數(shù)創(chuàng)建對象并返回(functional pattern)
這種方式很簡單,在函數(shù)內(nèi)部先新建一個(gè)對象 var object = {},然后為這個(gè)對象設(shè)置屬性,最后返回這個(gè)對象
優(yōu)點(diǎn):a.最簡單最容易理解,甚至不需要理解js的原型特性,應(yīng)該作為最優(yōu)先考慮的對象創(chuàng)建方式;
b.生成對象可以有私有屬性
c.具有類似java中super.method()的訪問"父對象"方法的能力
缺點(diǎn):同一函數(shù)生成的多個(gè)對象不能在內(nèi)存中共享代碼。
Js代碼
附錄
推薦一些極好的關(guān)于JS面向?qū)ο蟮奈恼拢恳黄紘?yán)重推薦,尤其是crockford和他的《JavaScript: The Good Parts》)
crockford大師
MDN
微軟雜志
MSDN
阮一峰
|
|