讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議104:謹慎使用偽類 >

建議104:謹慎使用偽類

JavaScript的原型存在諸多矛盾,某些看起來有點像基於類的語言的複雜語法問題遮蔽了它的原型機制。原型不但不讓對像直接從其他對像繼承,反而插入了一個多餘的間接層,從而使構造器函數產生對象。當一個函數對像被創建時,Function構造器產生的函數對像會運行類似這樣的一些代碼:


this.prototype={constructor:this};


新函數對像被賦予一個prototype屬性,其值包含一個constructor屬性且屬性值為該新函數對象。該prototype對象是存放繼承特徵的地方。因為JavaScript語言沒有提供一種方法來確定哪個函數是用做構造器的,所以每個函數都會得到一個prototype對象,constructor屬性沒什麼太大作用,重要的是prototype對象。

定義一個構造器並擴展它的原型:


var Mammal=function(name){

this.name=name;

};

Mammal.prototype.get_name=function{

return this.name;

};

Mammal.prototype.says=function{

return this.saying||'';

};


構造實例:


var myMammal=new Mammal('mammal');

var name=myMammal.get_name;//'mammal'


構造另一個偽類來繼承Mammal,這是通過定義它的constructor函數並替換它的prototype為一個Mammal的實例來實現的。


var Cat=function(name){

this.name=name;

this.saying='meow';

};

Cat.prototype=new Mammal;

Cat.prototype.purr=function(n){

var i,s='';

for(i=0;i<n;i+=1){

if(s){

s+='-';

}

s+='r';

}

return s;

};

Cat.prototype.get_name=function{

return this.says+''+this.name+''+this.says;

};

var myCat=new Cat('cat');

var says=myCat.says;//'meow'

var purr=myCat.purr(5);//'r-r-r-r-r'

var name=myCat.get_name;//'meow cat meow'


偽類模式的本意是想向面向對像靠攏,但它看起來與面向對像格格不入。我們可以隱藏一些「醜陋」的細節,這是通過使用method方法定義一個inherits方法來實現的。


Function.method('inherits',function(Parent){

this.prototype=new Parent;

return this;

});


inherits和method方法都返回this,這樣就可以通過鏈式語法只用一行語句構造Cat。


var Cat=function(name){

this.name=name;

this.saying='meow';

}.inherits(Mammal).method('purr',function(n){

var i,s='';

for(i=0;i<n;i+=1){

if(s){

s+='-';

}

s+='r';

}

return s;

}).method('get_name',function{

return this.says+''+this.name+''+this.says;

});


隱藏了prototype操作細節,現在看起來就沒那麼怪異了。現在有了行為像「類」的構造器函數,但它們沒有私有環境,所有的屬性都是公開的,因此無法訪問父類super的方法。同時,使用構造器函數存在一個嚴重的隱患。如果在調用構造器函數時忘記了在前面加上new前級,那麼this將不會被綁定到一個新對像上,而是被綁定到全局對像上,這樣不但沒有擴充新對象,反而會破壞全局變量。發生這種情況時,既沒有編譯時警告,也沒有運行時警告。

這是一個嚴重的語言設計錯誤。為了降低這個問題帶來的風險,所有的構造器函數都約定命名成首字母大寫的形式,並且不以首字母大寫的形式拼寫任何其他的東西。這樣至少可以通過人工檢查去發現是否缺少了new前綴。當然,一個更好的備選方案就是根本不使用new。

偽類形式可以給不熟悉JavaScript的程序員提供便利,但它也隱藏了該語言的真實本質。借鑒類的表示法可能誤導程序員去編寫過於深入和複雜的層次結構。許多複雜的類層次結構的產生就是源於靜態類型檢查的約束。JavaScript完全擺脫了這些約束。在基於類的語言中,類的繼承是代碼重用的唯一方式。JavaScript有著更多且更好的選擇。