讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議69:使用模塊化規避缺陷 >

建議69:使用模塊化規避缺陷

使用函數和閉包可以構建模塊。所謂模塊,就是一個提供接口卻隱藏狀態與實現的函數或對象。通過使用函數構建模塊,可以完全摒棄全局變量的使用,從而規避JavaScript語言缺陷。全局變量是JavaScript最為糟糕的特性之一,在一個大中型Web應用中,全局變量可能會帶來不可預料的後果。

例如,要為String擴展一個deentityify方法,其設計任務是尋找字符串中的HTML字符實體並將其替換為對應的字符。在一個對像中保存字符實體的名字及與之對應的字符是有意義的。

可以把deentityify放到一個全局變量中,但全局變量存在很多潛在危害。可以把deentityify定義在該函數本身中,但會帶來運行時的損耗,因為在該函數每次被執行時,這個方法都會被求值一次。理想的方式是將deentityify放入一個閉包,而且也許還能提供一個增加更多字符實體的擴展方法。


String.method('deentityify',function{

var entity={

quot:'"',

lt:'<',

gt:'>'

};

return function{

return this.replace(/&([^&;]+);/g,function(a,b){

var r=entity[b];

return typeof r==='string'?r:a;

});

};

});


在上面代碼中,為String類型擴展了一個deentityify方法,它調用字符串的replace方法來查找以「&」開頭和以「;」結束的子字符串。如果這些字符可以在字符實體表entity中找到,那麼就將該字符實體替換為映射表中的值。deentityify方法用到了一個正則表達式:


return this.replace(/&([^&;]+);/g,function(a,b){

var r=entity[b];

return typeof r==='string'?r:a;

});


在最後一行使用運算符立刻調用剛剛構造出來的函數。這個調用所創建並返回的函數才是deentityify方法。


document.writeln('<">'.deentityify);//<">


模塊利用了函數作用域和閉包來創建綁定對象與私有成員的關聯。在這個示例中,只有deentityify方法才有權訪問字符實體表entity這個數據對象。模塊開發的一般形式是:一個定義了私有變量和函數的函數,利用閉包創建可以訪問到的私有變量和函數的特權函數,最後返回這個特權函數,或者把它們保存到可訪問的地方。

使用模塊可以避免全局變量的濫用,從而保護信息的安全性,實現優秀的設計實踐。使用這種模式也可以實現應用程序的封裝,或者構建其他實例對象。

模塊模式通常結合實例模式使用。JavaScript的實例就是用對像字面量表示法創建的,對象的屬性值可以是數值或函數,並且屬性值在該對象的生命週期中不會發生變化。模塊通常作為工具為程序其他部分提供功能支持。通過這種方式能夠構建比較安全的對象。

下面代碼構造一個用來產生序列號的對象。serial_maker函數將返回一個用來產生唯一字符串的對象,這個字符串由兩部分組成:字符前綴+序列號。這兩部分可以分別使用set_prefix和set_seq方法進行設置,然後調用實例對象的gensym方法讀取這個字符串。當執行該方法時,都會自動產生唯一一個字符串。


var serial_maker=function{

var prefix='';

var seq=0;

return{

set_prefix:function(p){

prefix=String(p);

},

set_seq:function(s){

seq=s;

},

gensym:function{

var result=prefix+seq;

seq+=1;

return result;

}

};

};

var seqer=serial_maker;

seqer.set_prefix('Q');

seqer.set_seq(1000);

var unique=seqer.gensym;//"Q1000"

var unique=seqer.gensym;//"Q1001"


seqer包含的方法都沒有用到this或that,因此沒有辦法「損害」seger,除非調用對應的方法,否則無法改變prefix或seq的值。由於seqer對象是可變的,所以它的方法可能會被替換掉,但替換後的方法依然不能訪問私有成員。seqer就是一組函數的集合,而且這些函數被授予特權,擁有使用或修改私有狀態的能力。如果把seqer.gensym作為一個值傳遞給第三方函數,這個函數就能通過它產生唯一字符串,卻不能通過它來改變prefix或seq的值。