电竞比分网-中国电竞赛事及体育赛事平台

分享

淺談JavaScript類(lèi)的繼承

 歪SIR 2011-09-19
利用共享prototype實(shí)現(xiàn)繼承
繼承是面向?qū)ο箝_(kāi)發(fā)的又一個(gè)重要概念,它可以將現(xiàn)實(shí)生活的概念對(duì)應(yīng)到程序邏輯中。例如水果是一個(gè)類(lèi),具有一些公共的性質(zhì);而蘋(píng)果也是一類(lèi),但它們屬于水果,所以蘋(píng)果應(yīng)該繼承于水果。powered by 25175.net
在JavaScript中沒(méi)有專(zhuān)門(mén)的機(jī)制來(lái)實(shí)現(xiàn)類(lèi)的繼承,但可以通過(guò)拷貝一個(gè)類(lèi)的prototype到另外一個(gè)類(lèi)來(lái)實(shí)現(xiàn)繼承。一種簡(jiǎn)單的實(shí)現(xiàn)如下:
fucntion class1(){
      //構(gòu)造函數(shù)
}

function class2(){
      //構(gòu)造函數(shù)
}
class2.prototype=class1.prototype;
class2.prototype.moreProperty1="xxx";
class2.prototype.moreMethod1=function(){
      //方法實(shí)現(xiàn)代碼
}
var obj=new class2();
這樣,首先是class2具有了和class1一樣的prototype,不考慮構(gòu)造函數(shù),兩個(gè)類(lèi)是等價(jià)的。隨后,又通過(guò)prototype給class2賦予了兩個(gè)額外的方法。所以class2是在class1的基礎(chǔ)上增加了屬性和方法,這就實(shí)現(xiàn)了類(lèi)的繼承。
JavaScript提供了instanceof操作符來(lái)判斷一個(gè)對(duì)象是否是某個(gè)類(lèi)的實(shí)例,對(duì)于上面創(chuàng)建的obj對(duì)象,下面兩條語(yǔ)句都是成立的:
obj instanceof class1
obj instanceof class2
表 面上看,上面的實(shí)現(xiàn)完全可行,JavaScript也能夠正確的理解這種繼承關(guān)系,obj同時(shí)是class1和class2的實(shí)例。事是上不 對(duì),JavaScript的這種理解實(shí)際上是基于一種很簡(jiǎn)單的策略??聪旅娴拇a,先使用prototype讓class2繼承于class1,再在 class2中重復(fù)定義method方法:
<script language="JavaScript" type="text/javascript">
<!--
//定義class1
function class1(){
      //構(gòu)造函數(shù)
}
//定義class1的成員
class1.prototype={
      m1:function(){
            alert(1);
      }
}
//定義class2
function class2(){
      //構(gòu)造函數(shù)
}
//讓class2繼承于class1
class2.prototype=class1.prototype;
//給class2重復(fù)定義方法method
class2.prototype.method=function(){
      alert(2);
}
//創(chuàng)建兩個(gè)類(lèi)的實(shí)例
var obj1=new class1();
var obj2=new class2();
//分別調(diào)用兩個(gè)對(duì)象的method方法
obj1.method();
obj2.method();
//-->
</script>
從 代碼執(zhí)行結(jié)果看,彈出了兩次對(duì)話(huà)框“2”。由此可見(jiàn),當(dāng)對(duì)class2進(jìn)行prototype的改變時(shí),class1的prototype也隨之改變,即 使對(duì)class2的prototype增減一些成員,class1的成員也隨之改變。所以class1和class2僅僅是構(gòu)造函數(shù)不同的兩個(gè)類(lèi),它們保 持著相同的成員定義。從這里,相信讀者已經(jīng)發(fā)現(xiàn)了其中的奧妙:class1和class2的prototype是完全相同的,是對(duì)同一個(gè)對(duì)象的引用。其實(shí) 從這條賦值語(yǔ)句就可以看出來(lái):
//讓class2繼承于class1
class2.prototype=class1.prototype;
在 JavaScript中,除了基本的數(shù)據(jù)類(lèi)型(數(shù)字、字符串、布爾等),所有的賦值以及函數(shù)參數(shù)都是引用傳遞,而不是值傳遞。所以上面的語(yǔ)句僅僅是讓 class2的prototype對(duì)象引用class1的prototype,造成了類(lèi)成員定義始終保持一致的效果。從這里也看到了instanceof 操作符的執(zhí)行機(jī)制,它就是判斷一個(gè)對(duì)象是否是一個(gè)prototype的實(shí)例,因?yàn)檫@里的obj1和obj2都是對(duì)應(yīng)于同一個(gè)prototype,所以它們 instanceof的結(jié)果都是相同的。
因此,使用prototype引用拷貝實(shí)現(xiàn)繼承不是一種正確的辦法。但在要求不嚴(yán)格的情況下,卻也是一種合理的方法,惟一的約束是不允許類(lèi)成員的覆蓋定義。下面一節(jié),將利用反射機(jī)制和prototype來(lái)實(shí)現(xiàn)正確的類(lèi)繼承。
利用反射機(jī)制和prototype實(shí)現(xiàn)繼承
前 面一節(jié)介紹的共享prototype來(lái)實(shí)現(xiàn)類(lèi)的繼承,不是一種很好的方法,畢竟兩個(gè)類(lèi)是共享的一個(gè)prototype,任何對(duì)成員的重定義都會(huì)互相影響, 不是嚴(yán)格意義的繼承。但在這個(gè)思想的基礎(chǔ)上,可以利用反射機(jī)制來(lái)實(shí)現(xiàn)類(lèi)的繼承,思路如下:利用for(…in…)語(yǔ)句枚舉出所有基類(lèi)prototype的 成員,并將其賦值給子類(lèi)的prototype對(duì)象。例如:
<script language="JavaScript" type="text/javascript">
<!--
function class1(){
      //構(gòu)造函數(shù)
}
class1.prototype={
      method:function(){
           alert(1);
      },
      method2:function(){
           alert("method2");
      }
}
function class2(){
      //構(gòu)造函數(shù)
}
//讓class2繼承于class1
for(var p in class1.prototype){
       class2.prototype[p]=class1.prototype[p];
}

//覆蓋定義class1中的method方法
class2.prototype.method=function(){
      alert(2);
}
//創(chuàng)建兩個(gè)類(lèi)的實(shí)例
var obj1=new class1();
var obj2=new class2();
//分別調(diào)用obj1和obj2的method方法
obj1.method();
obj2.method();
//分別調(diào)用obj1和obj2的method2方法
obj1.method2();
obj2.method2();
//-->
</script>
從 運(yùn)行結(jié)果可見(jiàn),obj2中重復(fù)定義的method已經(jīng)覆蓋了繼承的method方法,同時(shí)method2方法未受影響。而且obj1中的method方法 仍然保持了原有的定義。這樣,就實(shí)現(xiàn)了正確意義的類(lèi)的繼承。為了方便開(kāi)發(fā),可以為每個(gè)類(lèi)添加一個(gè)共有的方法,用以實(shí)現(xiàn)類(lèi)的繼承:
//為類(lèi)添加靜態(tài)方法inherit表示繼承于某類(lèi)
Function.prototype.inherit=function(baseClass){
     for(var p in baseClass.prototype){
            this.prototype[p]=baseClass.prototype[p];
     }
}
這里使用所有函數(shù)對(duì)象(類(lèi))的共同類(lèi)Function來(lái)添加繼承方法,這樣所有的類(lèi)都會(huì)有一個(gè)inherit方法,用以實(shí)現(xiàn)繼承,讀者可以仔細(xì)理解這種用法。于是,上面代碼中的:
//讓class2繼承于class1
for(var p in class1.prototype){
       class2.prototype[p]=class1.prototype[p];
}
可以改寫(xiě)為:
//讓class2繼承于class1
class2.inherit(class1)
這樣代碼邏輯變的更加清楚,也更容易理解。通過(guò)這種方法實(shí)現(xiàn)的繼承,有一個(gè)缺點(diǎn),就是在class2中添加類(lèi)成員定義時(shí),不能給prototype直接賦值,而只能對(duì)其屬性進(jìn)行賦值,例如不能寫(xiě)為:
class2.prototype={
      //成員定義
}
而只能寫(xiě)為:
class2.prototype.propertyName=someValue;
class2.prototype.methodName=function(){
      //語(yǔ)句
}
由 此可見(jiàn),這樣實(shí)現(xiàn)繼承仍然要以犧牲一定的代碼可讀性為代價(jià),在下一節(jié)將介紹prototype-1.3.1框架(注:prototype-1.3.1框架 是一個(gè)JavaScript類(lèi)庫(kù),擴(kuò)展了基本對(duì)象功能,并提供了實(shí)用工具詳見(jiàn)附錄。)中實(shí)現(xiàn)的類(lèi)的繼承機(jī)制,不僅基類(lèi)可以用對(duì)象直接賦值給 property,而且在派生類(lèi)中也可以同樣實(shí)現(xiàn),使代碼邏輯更加清晰,也更能體現(xiàn)面向?qū)ο蟮恼Z(yǔ)言特點(diǎn)。

prototype-1.3.1框架中的類(lèi)繼承實(shí)現(xiàn)機(jī)制
在prototype-1.3.1框架中,首先為每個(gè)對(duì)象都定義了一個(gè)extend方法:
//為Object類(lèi)添加靜態(tài)方法:extend
Object.extend = function(destination, source) {
  for(property in source) {
     destination[property] = source[property];
  }
  return destination;
}
//通過(guò)Object類(lèi)為每個(gè)對(duì)象添加方法extend
Object.prototype.extend = function(object) {
  return Object.extend.apply(this, [this, object]);
}
Object.extend 方法很容易理解,它是Object類(lèi)的一個(gè)靜態(tài)方法,用于將參數(shù)中source的所有屬性都賦值到destination對(duì)象中,并返回 destination的引用。下面解釋一下Object.prototype.extend的實(shí)現(xiàn),因?yàn)镺bject是所有對(duì)象的基類(lèi),所以這里是為所 有的對(duì)象都添加一個(gè)extend方法,函數(shù)體中的語(yǔ)句如下:
Object.extend.apply(this,[this,object]);
這 一句是將Object類(lèi)的靜態(tài)方法作為對(duì)象的方法運(yùn)行,第一個(gè)參數(shù)this是指向?qū)ο髮?shí)例自身;第二個(gè)參數(shù)是一個(gè)數(shù)組,包括兩個(gè)元素:對(duì)象本身和傳進(jìn)來(lái)的 對(duì)象參數(shù)object。函數(shù)功能是將參數(shù)對(duì)象object的所有屬性和方法賦值給調(diào)用該方法的對(duì)象自身,并返回自身的引用。有了這個(gè)方法,下面看類(lèi)繼承的 實(shí)現(xiàn):
<script language="JavaScript" type="text/javascript">
<!--
//定義extend方法
Object.extend = function(destination, source) {
  for (property in source) {
     destination[property] = source[property];
  }
  return destination;
}
Object.prototype.extend = function(object) {
  return Object.extend.apply(this, [this, object]);
}
//定義class1
function class1(){
      //構(gòu)造函數(shù)
}
//定義類(lèi)class1的成員
class1.prototype={
      method:function(){
           alert("class1");
      },
      method2:function(){
           alert("method2");
      }

}
//定義class2
function class2(){
      //構(gòu)造函數(shù)
}
//讓class2繼承于class1并定義新成員
class2.prototype=(new class1()).extend({
      method:function(){
           alert("class2");
      }
});

//創(chuàng)建兩個(gè)實(shí)例
var obj1=new class1();
var obj2=new class2();
//試驗(yàn)obj1和obj2的方法
obj1.method();
obj2.method();
obj1.method2();
obj2.method2();
//-->
</script>
從運(yùn)行結(jié)果可以看出,繼承被正確的實(shí)現(xiàn)了,而且派生類(lèi)的額外成員也可以以列表的形式加以定義,提高了代碼的可讀性。下面解釋繼承的實(shí)現(xiàn):
//讓class2繼承于class1并定義新成員
class2.prototype=(new class1()).extend({
      method:function(){
           alert("class2");
      }
});
上段代碼也可以寫(xiě)為:
//讓class2繼承于class1并定義新成員
class2.prototype=class1.prototype.extend({
      method:function(){
           alert("class2");
      }
});
但 因?yàn)閑xtend方法會(huì)改變調(diào)用該方法對(duì)象本身,所以上述調(diào)用會(huì)改變class1的prototype的值,犯了和以前一樣的錯(cuò)誤。在 prototype-1.3.1框架中,巧妙的利用new class1()來(lái)創(chuàng)建一個(gè)實(shí)例對(duì)象,并將實(shí)例對(duì)象的成員賦值給class2的prototype。其本質(zhì)相當(dāng)于創(chuàng)建了class1的prototype 的一個(gè)拷貝,在這個(gè)拷貝上進(jìn)行操作自然不會(huì)影響原有類(lèi)中prototype的定義了。

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多