一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

深刻理解JavaScript基于原型的面向?qū)ο?/span>

 hqpek 2017-08-12

 



主題一、原型

一、基于原型的語言的特點(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代碼  收藏代碼
  1. newObject = create(oldObject);  
 
那么新對象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代碼  收藏代碼
  1. function F(){};  
  2. F.prototype = oldObject;  
  3. var newObject = new F();  
這太繁瑣了?;谠驼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. class Empolyee{  
  2.     String name;  
  3.     public Employee(String name){  
  4.         this.name = name;  
  5.     }  
  6.     public getName(){  
  7.         return this.name;  
  8.     }  
  9. }  
  10. class Coder extends Employee {  
  11.     String language;  
  12.     public Coder(name,language){  
  13.         super(name);  
  14.         this.language = language;  
  15.     }  
  16.     public getLanguage(){  
  17.         return this.language;  
  18.     }  
  19. }  
 
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代碼  收藏代碼
  1. function Empolyee(name){  
  2.     this.name = name;  
  3. }  
 
我們只要生成一個(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代碼  收藏代碼
  1. function Empolyee(name){  
  2.     this.name = name;  
  3. }  
  4. var employee = {};  
  5. Employee.call(employee,'Jack');  
 
    
3) 到這里,以類似java方式產(chǎn)生對象基本完成了,但是這個(gè)employee對象沒有方法
我們的function是第一類對象,可以運(yùn)行時(shí)創(chuàng)建,可以當(dāng)做變量賦值,所以沒有問題
Js代碼  收藏代碼
  1. function Empolyee(name){  
  2.     this.name = name;  
  3.     this.getName = function(){return this.name};  
  4. }  
 
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. function Coder extends Employee(name,language){  
  2.     super(name);  
  3.     this.language = language;  
  4. }  
 
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代碼  收藏代碼
  1. var topObject = {  
  2.     __version__ : 1.0;  
  3. };  
  4.   
  5. function Empolyee(name){  
  6.     this.name = name;  
  7.     this.getName = function(){return this.name};  
  8. }  
  9. var employee = {};  
  10. employee.__proto__ = topObject;  
  11. Employee.call(employee,'Jack');  
  12.   
  13. function Coder(name,language){  
  14.     this.name = name;  
  15.     this.language = this.language;  
  16.     this.getLanguage = function(){return this.language};  
  17. }  
  18.   
  19. var coder = {};  
  20. coder.__proto__ = employee;  
  21. Coder.call(coder,'Coder Jack','Java');  
 
當(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代碼  收藏代碼
  1. function sliceArguments(argumentsObj,n){  
  2.     var args = [];  
  3.     for(var i=0;i<argumentsObj.length;i++){  
  4.         if(i>=n){  
  5.             args.push(argumentsObj[i]);  
  6.         }  
  7.     }  
  8. }  
  9. function newInstance(prototype,constructor){  
  10.     var obj = {};  
  11.     obj.__proto__ = prototype;  
  12.     constructor.apply(obj,sliceArguments(arguments,2));  
  13. }  
  14. var employee = newInstance(topObject,Employee,'Jack');  
  15. var coder = newInstance(employee,Coder,'Coder Jack','Java');  
 
優(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代碼  收藏代碼
  1. function newInstance(constructor){  
  2.     var obj = {};  
  3.     obj.__proto__ = constructor.prototype;  
  4.     constructor.apply(obj,sliceArguments(arguments,1));  
  5.     return obj;  
  6. }  
  7. function Employee(name){  
  8.     this.name = name;  
  9.     this.getName = function(){return this.name};  
  10. }  
  11. var employee = newInstance(Empolyee,'Jack');  
  12. var employee2 = newInstance(Empolyee,'Jack2');  
  13. var employee3 = newInstance(Empolyee,'Jack3');  
  14. function Coder(name,language){  
  15.     this.name = name;  
  16.     this.language = language;  
  17.     this.getLanguage = function(){return this.language};  
  18. }  
  19. Coder.prototype = newInstance(Empolyee,'');  
  20.   
  21. var coder = newInstance(Coder,'Coder Jack','Java');  
  22. var coder2 = newInstance(Coder,'Coder Lee','C#');  
  23. var coder3 = newInstance(Coder,'Coder Liu','C++');  
  24. var coder4 = newInstance(Coder,'Coder Liu','JavaScript');  
 
到達(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代碼  收藏代碼
  1. function Employee(name){  
  2.     this.name = name;  
  3.     this.getName = function(){return this.name};  
  4. }  
  5. var employee = new Employee('Jack');  
 
分析上面代碼。
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代碼  收藏代碼
  1. function newInstance(constructor){  
  2.         var obj = {};  
  3.         obj.__proto__ = constructor.prototype;  
  4.         constructor.call(obj,sliceArguments(arguments,1));  
  5.         return obj;  
  6. }  
 
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代碼  收藏代碼
  1. {  
  2.     __proto__:Object.prototype,  
  3.     constructor: 指向函數(shù)本身  
  4. }  
 
__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代碼  收藏代碼
  1. f.prototype.constructor=f  
 
   以確保維護(hù)正確的三角關(guān)系。
   例如 
Js代碼  收藏代碼
  1. function Employee(){};  
  2. function Coder(){};  
  3. Coder.prototype = new Employee();  
  4. Coder.prototype.constructor = Coder;  
  5. var coder = new Coder();  
 
   但是經(jīng)過我的測試,即使不寫上一行Coder.prototype.constructor = Coder;,以下測試都表現(xiàn)正確
Js代碼  收藏代碼
  1. coder instanceOf Coder//true  
  2. Coder.prototype.isPrototypeOf(coder)//true  
 
                   也就是說原型對象的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代碼  收藏代碼
  1. <script>  
  2.     function Employee(name){  
  3.         this.name = name;  
  4.         //this.getName = function(){return this.name};方法代碼應(yīng)該放到原型對象之中,而不是初始化函數(shù)中,這樣每個(gè)employee對象都共享同一個(gè)方法代碼  
  5.     }  
  6.     Employee.prototype.getName = function(){return this.name};  
  7.     var employee = new Employee('Jack');  
  8.     console.log("employee.getName(): " + employee.getName());//Jack  
  9.     var employee2 = new Employee('Jack2');  
  10.     console.log("employee2.getName(): " + employee2.getName());//Jack2    
  11.     function Coder(name,language){  
  12.         this.name = name;  
  13.         this.language = language;  
  14.         //this.getLanguage = function(){return this.language}; 方法代碼應(yīng)該放到原型對象之中,而不是初始化函數(shù)中,這樣才能實(shí)現(xiàn)代碼共享  
  15.     }  
  16.     Coder.prototype = new Employee('');  
  17.     Coder.prototype.constructor = Coder;//這一句話其實(shí)也可以不寫,不影響繼承  
  18.     Coder.prototype.getLanguage = function(){return this.language};  
  19.   
  20.     var coder = new Coder('Coder Jack','Java');  
  21.     console.log("coder.getName(): " + coder.getName());//Coder Jack  
  22.     console.log("coder.getLanguage(): "+coder.getLanguage());//Java  
  23.     var coder2 = new Coder('Coder Lee','C#');  
  24.     console.log("coder2.getName(): " + coder2.getName());//Coder Lee  
  25.     console.log("coder2.getLanguage(): " + coder2.getLanguage());//C#  
  26.     var coder3 = new Coder('Coder Liu','C++');  
  27.     console.log("coder3.getLanguage(): " + coder3.getName());//Coder Liu  
  28.     console.log("coder3.getLanguage()" + coder3.getLanguage());//C++  
  29.       
  30.     console.log("employee.constructor: " + employee.constructor);  
  31.     console.log("employee.constructor.prototype === Employee.prototype: " + (employee.constructor.prototype === Employee.prototype));  
  32.     console.log("employee.constructor.prototype.constructor === Employee: " + (employee.constructor.prototype.constructor === Employee));  
  33.     console.log("employee instanceof Object: "  + (employee instanceof Object));  
  34.     console.log("employee instanceof Function: "  + (employee instanceof Function));  
  35.     console.log("employee instanceof Employee: "  + (employee instanceof Employee ));  
  36.     console.log("Employee.prototype.isPrototypeOf(employee): "  + (Employee.prototype.isPrototypeOf(employee)));  
  37.     console.log("Function.prototype.isPrototypeOf(employee): "  + (Function.prototype.isPrototypeOf(employee)));  
  38.     console.log("Object.prototype.isPrototypeOf(employee): "  + (Object.prototype.isPrototypeOf(employee)));  
  39.     console.log("coder.constructor: " + coder.constructor);  
  40.     console.log("coder instanceof Object: "  + (coder instanceof Object));  
  41.     console.log("coder instanceof Function: "  + (coder instanceof Function));  
  42.     console.log("coder instanceof Employee: "  + (coder instanceof Employee ));  
  43.     console.log("coder instanceof Coder: "  + (coder instanceof Coder ));  
  44.     console.log("Employee.prototype.isPrototypeOf(coder): "  + (Employee.prototype.isPrototypeOf(coder)));  
  45.     console.log("Coder.prototype.isPrototypeOf(coder): "  + (Coder.prototype.isPrototypeOf(coder)));  
  46.     console.log("Function.prototype.isPrototypeOf(coder): "  + (Function.prototype.isPrototypeOf(coder)));  
  47.     console.log("Object.prototype.isPrototypeOf(coder): "  + (Object.prototype.isPrototypeOf(coder)));  
  48.   </script>  
 
  
  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代碼  收藏代碼
  1. if (typeof Object.create !== 'function') {  
  2.     Object.create = function (o) {  
  3.         function F() {}  
  4.         F.prototype = o;  
  5.         return new F();  
  6.     };  
  7. }  
  8. var newObject = Object.create(oldObject);  
 
我要讓讀者知道,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代碼  收藏代碼
  1. <script>  
  2. /** 
  3.  * 以原型對象為模板創(chuàng)建出新對象 
  4.  * 這個(gè)函數(shù)已經(jīng)被Chrome和IE9采用,所以需要有個(gè)判斷這個(gè)函數(shù)是否已經(jīng)存在,Crockford的影響力可見一斑 
  5.  */  
  6. if(!Object.create){  
  7.     Object.create = function(oldObject){  
  8.         function F(){};  
  9.         F.prototype = oldObject;  
  10.         return new F();  
  11.     }  
  12. }  
  13. /** 
  14.  * 在構(gòu)造函數(shù)的原型對象上添加方法 
  15.  * 非常推薦這個(gè)函數(shù),因?yàn)檫@個(gè)函數(shù)能夠培養(yǎng)出在原型對象中定義方法的良好習(xí)慣 
  16.  */  
  17. Function.prototype.method = function(name,func){  
  18.     if(!this.prototype[name]){  
  19.         this.prototype[name] = func;  
  20.         return this;  
  21.     }  
  22. };  
  23. /** 
  24.  * 使構(gòu)造函數(shù)“繼承”其他構(gòu)造函數(shù) 
  25.  * 實(shí)際上是將構(gòu)造函數(shù)的原型對象替換為另外構(gòu)造函數(shù)產(chǎn)生的對象 
  26.  *  
  27.  */  
  28. Function.method('inherits',function(F){  
  29.     this.prototype = new F();  
  30.     return this;  
  31. });  
  32.   
  33. /***************************************** 
  34.  *使用鏈?zhǔn)酱a清晰緊湊地定義構(gòu)造函數(shù) 
  35.  *****************************************/  
  36. var Employee = function(name){  
  37.     this.name = name;  
  38. }.method('getName',function(){  
  39.     return this.name;  
  40. });  
  41.   
  42. //由于method和inherits函數(shù)都返回this,所以可以非常舒服地將構(gòu)造函數(shù)寫成鏈?zhǔn)酱a  
  43. var employee = new Employee("jack");  
  44. alert(employee.getName());  
  45.   
  46. //由于method和inherits函數(shù)都返回this,所以可以非常舒服地將構(gòu)造函數(shù)寫成鏈?zhǔn)酱a  
  47. var Coder = function(name,language){  
  48.     this.name = name;  
  49.     this.language = language;  
  50. }.inherits(Employee)  
  51.  .method('getLanguage',function(){  
  52.     return this.language;  
  53.  })  
  54.  .method('getIntroduction',function(){  
  55.     return this.name + " is skilled in " + this.language;  
  56.  });  
  57.   
  58.   var coder = new Coder('Jack','Java');  
  59.   alert(coder.getIntroduction());  
  60.   alert(coder.getName());  
  61.   
  62.  </script>  
 
  增強(qiáng)1.模擬私有變量。
上面構(gòu)造函數(shù)所產(chǎn)生的對象只有public成員,沒有private成員,可以通過閉包實(shí)現(xiàn)私有成員
Js代碼  收藏代碼
  1.     /***************************************** 
  2.  * 模擬私有變量 
  3.  *****************************************/  
  4. var Employee = function(name){  
  5.     //私有變量  
  6.     var name = name;              
  7.     this.getName = function(){return name};  
  8. };  
  9. var employee = new Employee('Jack');  
  10. alert(employee.name);//undefined  
  11. alert(employee.getName());//Jack  
 
私有成員帶來的代價(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代碼  收藏代碼
  1. /****************** 
  2.          *模擬super.method() 
  3.          ******************/  
  4. var Coder = function(name,language){  
  5.     var employee = new Employee('');  
  6.     //父類的getName方法  
  7.     var superGetName = employee.getName;  
  8.     this.name = name;  
  9.     this.language = language;  
  10.     this.getName = function(){  
  11.         return "my name is :" + superGetName.call(this,name);  
  12.     };  
  13. }.inherits(Employee)  
  14.  .method('getLanguage',function(){  
  15.     return this.language;  
  16.  })  
  17.  .method('getIntroduction',function(){  
  18.     return this.name + " is skilled in " + this.language;  
  19.  });  
  20.   
  21.   var coder = new Coder('Jack','Java');  
  22.   alert(coder.getIntroduction());  
  23.   alert(coder.getName());//my name is Jack  
 
2) 使用原型創(chuàng)建對象(prototypal pattern)

這種創(chuàng)建方式直白地顯示了原型語言創(chuàng)建對象的特點(diǎn)

Js代碼  收藏代碼
  1. <script>  
  2. /** 
  3.  * 以原型對象為模板創(chuàng)建出新對象 
  4.  */  
  5. if(!Object.create){  
  6.     Object.create = function(oldObject){  
  7.         function F(){};  
  8.         F.prototype = oldObject;  
  9.         return new F();  
  10.     }  
  11. }  
  12.   
  13. /***************************************** 
  14.  * 使用原型對象創(chuàng)建對象,創(chuàng)建之后再對象初始化, 
  15.  * 這種創(chuàng)建方式直白地顯示了原型語言創(chuàng)建對象的特點(diǎn) 
  16.  *****************************************/  
  17.   
  18. var employee = {  
  19.     name: 'Jack',  
  20.     getName: function(){return this.name;}  
  21. };  
  22.   
  23. var coder = Object.create(employee);  
  24. coder.name = 'Jackson';  
  25. coder.language = 'language';  
  26. coder.getLanguage = 'Java';  
  27. coder.getIntroduction = function(){  
  28.     return this.name + " is skilled in " + this.language;  
  29. }  
  30. alert(coder.getName());  
  31. alert(coder.getIntroduction());  
  32.  </script>  
 

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代碼  收藏代碼
  1. <script>  
  2.         /** 
  3.      * 以原型對象為模板創(chuàng)建出新對象 
  4.      * 這個(gè)函數(shù)已經(jīng)被Chrome和IE9采用,所以需要有個(gè)判斷這個(gè)函數(shù)是否已經(jīng)存在,Crockford的影響力可見一斑 
  5.      */  
  6.     if(!Object.create){  
  7.         Object.create = function(oldObject){  
  8.             function F(){};  
  9.             F.prototype = oldObject;  
  10.             return new F();  
  11.         }  
  12.     }  
  13.     /** 
  14.      * 在構(gòu)造函數(shù)的原型對象上添加方法 
  15.      * 非常推薦這個(gè)函數(shù),因?yàn)檫@個(gè)函數(shù)能夠培養(yǎng)出在原型對象中定義方法的良好習(xí)慣 
  16.      */  
  17.     Function.prototype.method = function(name,func){  
  18.         if(!this.prototype[name]){  
  19.             this.prototype[name] = func;  
  20.             return this;  
  21.         }  
  22.     };  
  23.     /** 
  24.      * 使構(gòu)造函數(shù)“繼承”其他構(gòu)造函數(shù) 
  25.      * 實(shí)際上是將構(gòu)造函數(shù)的原型對象替換為另外構(gòu)造函數(shù)產(chǎn)生的對象 
  26.      *  
  27.      */  
  28.     Function.method('inherits',function(F){  
  29.         this.prototype = new F();  
  30.         return this;  
  31.     });  
  32.       
  33.     /** 
  34.      * 創(chuàng)建父對象方法的副本 
  35.      */  
  36.     Object.method('superior',function(methodName){  
  37.         var that  =  this;  
  38.         var method = this[methodName];  
  39.         return function(){  
  40.             return method.apply(that,arguments);  
  41.         };  
  42.     });  
  43.     /***************************************** 
  44.      * 使用函數(shù)創(chuàng)建對象 
  45.      * 1 使用函數(shù)的閉包實(shí)現(xiàn)私有屬性 
  46.      * 2 子對象可以調(diào)用父對象的方法 
  47.      *****************************************/  
  48.     function employee(name){  
  49.         var object = {};  
  50.         //name屬性是私有變量  
  51.         var name = name;  
  52.         //定義一個(gè)getName私有變量的目的是,如果其他方法想調(diào)用getName方法,它們可以直接調(diào)用getName而不是object.getName。  
  53.         //如果該object.getName被外部篡改了,那么其他引用var getName的方法并不會(huì)收到影響,這樣程序的健壯性有保證  
  54.         var getName = function(){  
  55.             return name;  
  56.         }  
  57.         //getName對外公開  
  58.         object.getName = getName;  
  59.         return object;  
  60.     }  
  61.   
  62.     function coder(name,language){  
  63.         var object = employee(name);  
  64.         //獲取父對象getName函數(shù)的副本  
  65.         var superGetName = object.superior('getName');  
  66.         var language = language;  
  67.         var getLanguage = function(){return language;};  
  68.         //調(diào)用父對象的方法  
  69.         var getName = function(){  
  70.             return  "my name is " + superGetName(name);  
  71.         };  
  72.   
  73.         object.getName = getName;  
  74.   
  75.         return object;  
  76.     }  
  77.     var e1 = employee('Jack');  
  78.     alert(e1.name);//undefined  
  79.     alert(e1.getName());//Jack  
  80.       
  81.     var c1 = coder('Jackson','Java');  
  82.     alert(c1.getName());//My name is Jack  
  83.       
  84.   </script>  
  附錄
推薦一些極好的關(guān)于JS面向?qū)ο蟮奈恼拢恳黄紘?yán)重推薦,尤其是crockford和他的《JavaScript: The Good Parts》)

crockford大師

MDN

微軟雜志

MSDN

阮一峰

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多

    欧洲自拍偷拍一区二区| 少妇视频一区二区三区| 日本不卡一本二本三区| 成人免费观看视频免费| 国产自拍欧美日韩在线观看| 五月婷婷欧美中文字幕 | 欧美精品久久99九九| 中日韩美女黄色一级片| 98精品永久免费视频| 粉嫩国产美女国产av| 又黄又色又爽又免费的视频| 91亚洲精品国产一区| 国产精品免费自拍视频| 国产精品熟女乱色一区二区| 欧美日韩国产免费看黄片| 91人妻久久精品一区二区三区 | 亚洲国产天堂av成人在线播放| 亚洲中文字幕在线观看黑人| 久久精品国产99精品亚洲| 久久re6热在线视频| 婷婷伊人综合中文字幕| 欧美日韩成人在线一区| 日本高清加勒比免费在线| 麻豆视传媒短视频免费观看| 一区二区三区人妻在线| 国产精品午夜福利在线观看| 国产精品成人一区二区在线| 青青免费操手机在线视频| 夫妻激情视频一区二区三区| 色综合久久中文综合网| 厕所偷拍一区二区三区视频| 中文久久乱码一区二区| 欧美中文字幕一区在线| 真实偷拍一区二区免费视频| 美女激情免费在线观看| 色一情一伦一区二区三| 日韩欧美一区二区不卡看片| 精品欧美日韩一二三区 | 免费特黄欧美亚洲黄片| 欧美亚洲另类久久久精品 | 99久久国产亚洲综合精品|