讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議99:建議主動封裝類 >

建議99:建議主動封裝類

封裝(encapsulation)就是把對像內部數據和操作細節進行隱藏。很多面向對像語言都支持封裝,JavaScript不支持該特性,但使用閉包可以實現類型的封裝功能。

很多JavaScript程序喜歡類的被動封裝。所謂被動封裝,就是對對像內部數據進行適當約定,這種約定具有很強的主觀性,沒有強制性保證,這主要針對公共對像而言。一般來說,JavaScript類對像所包含的數據都是公開的,沒有隱私可言,任何人都可以訪問其中的信息。

為了數據安全,在代碼中適當增加了一些條件限制,避免非法侵入,當然可以增加更完善的監測方法,以保護輸入數據的完整性。


var Card=function(name,sex,work,detail){//較安全的公共類

if(!checkName(name))throw new Error(\"name值非法\"){

this.name=name;

}

this.sex=checkSex(sex);

this.work=checkWork(work);

this.detail=checkDetail(detail);

}

Card.prototype={//類內部數據檢測方法

checkName:function(name){//檢測name,參數為name,返回布爾值,檢測是否合法

}

checkSex:function(sex){//檢測sex,參數為sex,返回sex,檢測是否符合約定

}

checkWork:function(work){//檢測work,參數為work,返回work,檢測是否符合約定

}

checkDetail:function(detail){//檢測detail,參數為detail,返回detail,檢測是否符合約定

}

}


上面代碼僅列出了各種方法的框架。當然,從更安全和更擴展的角度來講,凡是類都應該定義接口,這樣才能確保數據存取更加安全,同時也方便與其他開發人員和用戶進行交流。

內部私有方法監測和接口措施能夠在一定程序上保護對像內部數據,但它們也存在一個致命的漏洞,即這些屬性和方法可以被公開重置。面對公開覆蓋屬性和方法值,任何人都無法阻止。不管操作是有意還是無意的,屬性都可能會被設置為無效值。同時內部檢測和接口在一定程度上佔用了系統開銷,這個問題也是需要必須認真考慮的。

很多開發人員習慣使用命名規範來區分公共成員與私有成員,即在一些方法和屬性的名稱前後加下畫線以示其私有特性。由於下畫線在JavaScript中可以用做標識符的第一個字符,因此它們仍然是有效的變量名。

下畫線命名法是一種約定俗成的命名規範,它表明一個屬性和方法僅供對像內部使用,直接訪問此屬性可能會導致意想不到的後果。雖然它不是強制性規定,但是有助於防止開發人員無意識的誤用。

上述數據保護的方法和措施都是被動性防禦,帶有很強的主觀性。因為它們只是一種約定,只有在得到遵守時才有效果,而且並沒有什麼強制性手段可以保證實施,所以它們不是真正可以用來隱藏對像內部數據的解決方案,主要適用於非敏感性的內部方法和屬性。

在JavaScript中,只有函數具有作用域。在函數內部聲明的變量,在函數外部是無權訪問的。從本質上分析,所謂私有屬性和私有方法,就是在對像外部無法訪問,因此要真正實現類的封裝設計要求,使用函數作用域是最佳選擇。

可以根據函數的這一特性,把上面示例中的私有數據用函數作用域和閉包進行封裝。實現方法:在函數結構體內部定義變量,這些變量可以被定義該作用域中的所有函數訪問。


var Card=function(name,sex,work,detail){//安全的類

var_name=name,_sex=sex,_work=work,_detail=detail;//私有屬性

function_checkName(_name){//私有方法

}

function_checkSex(_sex){//私有方法

}

function_checkWork(_work){//私有方法

}

function_checkDetail(_detail){//私有方法

}

if(!_checkName(_name))throw new Error(\"name值非法\"){

this.name=_name;

}

this.sex=_checkSex(_sex);

this.work=_checkWork(_work);

this.detail=_checkDetail(_detail);

}

Card.prototype={

//公共方法

}


要使外界可以訪問某些私有方法,可以採用如下方法來實現:


var Card=function(name,sex,work,detail){

var_name=name,_sex=sex,_work=work,_detail=detail;

function_checkName(_name){

}

this.checkName=function{//私有方法,實現外部調用

return_checkName

}

}


函數作用域內部的方法無權被外界訪問,但在函數作用域內的其他公共方法可以訪問內部方法,於是將公共方法作為中轉平台,可以巧妙地把內部私有方法公開化。因此,這些公共方法也稱為特權方法,即在方法的前面加上關鍵字this。因為這些方法被定義於函數作用域中,所以它們能夠訪問到私有屬性,對於不需要直接訪問私有屬性和方法的方法,建議將它們放在類的原型對像中進行聲明。

使用這種方式創建的對象具有真正的封裝特性,但它也有缺點:生成的每一個新實例對象都會為每一個私有方法和特權方法生成一個新的副本,這會佔用大量的系統資源,不適宜大量使用,僅在必要時適當使用。同時,這種方法不利於類的繼承,因為所有派生的子類都不能訪問超類的任何私有屬性和方法。例如:


var Card=function{//超類

var_name=1;

function_checkName{

return_name;

}

this.checkName=function{

return_name;

}

}

function F{//子類

Card.call(this);//繼承類Card

this.name=_name;

}

var a=new F;

alert(a.name);//訪問無效

alert(a._checkName);//無法訪問,拋出解析錯誤


不過,讀者可以通過特權方法來訪問超類中的私有屬性和方法:


alert(a.checkName);//訪問超類公共方法,間接訪問私有屬性和方法