首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》建议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);//访问超类公共方法,间接访问私有属性和方法