53节建议保持参数顺序的一致约定对于帮助程序员记住每个参数在函数调用中的意义很重要。参数较少这个主意不错,但如果参数过多后,就出现麻烦了,记忆和理解起来都不太容易。
参数蔓延
如下面这些代码:
var alert=new Alert(100,75,300,200,'Error',message,'blue','white','black','error',true);
这个通常是参数蔓延的结果。一个函数起初很简单,然而随着库功能的扩展,该函数的签名便会获得越来越多的参数。
选项对象
js提供了一个简单、轻量的惯用法:选项对象。选项对象在应对较大规模的函数签名时运作良好。一个选项参数就是一个通过其命名属性来提供额外参数数据的参数。对象字面量的形式使得读写选项对象尤其舒适。
var alert=new Alert({
x:100,y:75,
300,height:200,
title:'Error',message:message,
titleColor:'blue',bgColor:'white',textColor:'black',
icon:'error',modal:true
});
自我说明性
这样实现选项对象显得繁琐,但更容易阅读。每个参数都是自我描述的。不需要注释来解释各参数的职责,因为其属性名就是一种说明。对于布尔参数,如果只是传递true或false并不能提供让人明白它代表的意思,但用属性名modal可以更清晰地说明它的用途。
参数可选性
选项对象的所有参数都是可选的,调用者可以提供任一可选参数的子集。与普通参数(有时也叫位置参数,因为它们的位置是和形参一一对应的)相比,可选参数通常会引入一些歧义。例如,如果希望Alert对象的位置和大小属性都是可选的,那么很难理解如下的调用。
var alert=new Alert(app,150,150,'Error',message,'blue','white','black','error',true);
这里在使用可选参数时就造成了麻烦,因为不知道省略的是哪些参数,因为x,y和width,height无法区分。使用选项对象就没有任何问题(不依赖于参数的顺序,只依赖与参数的属性名)。
var alert=new Alert({
300,height:200,
title:'Error',message:message,
titleColor:'blue',bgColor:'white',textColor:'black',
icon:'error',modal:true
});
选项对象仅包括可选参数,因此省略掉整个对象甚至都是可能的。
var alert=new Alert();
如果只有一个或两个必选的参数,最好使它们独立于选项对象。
var alert=new Alert(app,message,{
300,height:200,
title:'Error',
titleColor:'blue',bgColor:'white',textColor:'black',
icon:'error',modal:true
});
实现一个接收选项对象的函数需要额外的代码处理。代码如下:
function Alert(parent,message,opts){
opts=opts||{};
this.width=opts.width===undefined?320:opts.width;
this.height=opts.height===undefined?240:opts.height;
this.x=opts.x===undefined?(parent.width)/2-(this.width/2):opts.x;
this.y=opts.y===undefined?(parent.height)/2-(this.height/2):opts.y;
this.title=opts.title||'Alert';
this.titleColor=opts.titleColor||'gray';
this.bgColor=opts.bgColor||'white';
this.textColor=opts.textColor||'black';
this.icon=opts.icon||'info';
this.modal=!!opts.modal;
this.message=message;
}
这里对opts使用了或(||)操作符提供了一个默认空选项对象。由于0是一个有效值但不是默认值,所以需要测试数值参数是否为undefined。基于空字符串是无效的、应该被默认值取代的假设,这里使用逻辑或来应对字符串参数。modal参数使用双重否定模式将其参数强制转换为一个布尔值。
extend函数
与位置参数对比,这段代码比较烦琐。可以使用有用的抽象来简化这些工作。(对象的扩展或合并函数)。比如许多库提供的extend函数。该函数接收一个target对象和一个source对象,并将后者的属性复制到前者中。该程序最有用的应用之一是抽象出合资默认值和用户提供的选项对象值的逻辑。借助extend函数,代码改写为
function Alert(parent,message,opts){
opts=extend({320,height:240});
opts=extend({
x:(parent.width)/2-(opts.width/2):opts.x,
y:(parent.height)/2-(opts.height/2):opts.y,
title:'Alert',
titleColor:'gray',
bgColor:'white',
textColor:'black',
icon:'info',
modal:false
},opts);
this.width=opts.width;
this.height=opts.height;
this.x=opts.x;
this.y=opts.y;
this.title=opts.title;
this.titleColor=opts.titleColor;
this.bgColor=opts.bgColor;
this.textColor=opts.textColor;
this.icon=opts.icon;
this.modal=opts.modal;
this.message=message;
}
这避免了不断地重复实现检查每个参数是否存在的逻辑。这里两次调用了extend函数,因为,x,y的默认值依赖于早前计算的width,height的值。
如果想要把整个opts复制到this对象,可以进一步简化。
function Alert(parent,message,opts){
opts=extend({320,height:240});
opts=extend({
x:(parent.width)/2-(opts.width/2):opts.x,
y:(parent.height)/2-(opts.height/2):opts.y,
title:'Alert',
titleColor:'gray',
bgColor:'white',
textColor:'black',
icon:'info',
modal:false
},opts);
extend(this,opts);
}
不同的框架提供的extend函数不同,典型的实现是枚举源对象的属性,并当这些属性不是undefined时将其复制到目标对象中。
function extend(target,source){
if(source){
for(var key in source){
var val=source[key];
if(typeof val !== 'undefined'){
target[key]=val;
}
}
}
return target;
}
区别
原来的Alert版本和使用extend函数实现的版本的区别
-
早期版本中的条件逻辑如果不需要默认值则会避免计算默认值。只要计算默认值对诸如修改用用户接口或发送网络请求没有影响,那么这不是一个问题。
-
判断一个值是否已经提供了的逻辑。在早前版本中,对于字符串参数,我们将空字符串视为undefined等价。只将undefined视为缺省的参数更恰当。使用或(||)操作符是一个提供默认参数值有效但非一致的策略。
一致性是库设计的一个良好目标,因为它会给api的使用者带来更好的可预测性。
提示
-
使用选项对象使得api更具可读性、更容易记忆
-
所有通过选项对象提供的参数应当被视为可选的
-
使用extend函数抽象出从选项对象中提取值的逻辑