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,
width: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({
width: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,{
width: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({width: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({width: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函数抽象出从选项对象中提取值的逻辑

相关阅读

[Effective JavaScript 笔记]第55条:接收关键字参数的选项对象的更多相关文章

  1. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  2. [Effective JavaScript 笔记]第23条:永远不要修改arguments对象

    arguments对象并不是标准的Array类型的实例.arguments对象不能直接调用Array方法. arguments对象的救星call方法 使得arguments可以品尝到数组方法的美味,知 ...

  3. [Effective JavaScript 笔记] 第8条:尽量少用全局对象

    初学者容易使用全局变量的原因 创建全局变量毫不费力,不需要任何形式的声明(只要在非函数里用var 你就可以得到一个全局变量) 写得代码简单,不涉及到大的项目或配合(写hello world是不会有什么 ...

  4. [Effective JavaScript 笔记]第24条:使用变量保存arguments对象

    迭代器(iterator)是一个可以顺序存取数据集合的对象.其一个典型的API是next方法.该方法获得序列中的下一个值. 迭代器示例 题目:希望编写一个便利的函数,它可以接收任意数量的参数,并为这些 ...

  5. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  6. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  7. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  8. [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

    对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...

  9. [Effective JavaScript 笔记]第53条:保持一致的约定

    对于api使用者来说,你所使用的命名和函数签名是最能产生普遍影响的决策.这些约定很重要具有巨大的影响力.它建立了基本的词汇和使用它们的应用程序的惯用法.库的使用者必须学会阅读和使用这些.一致的约定可以 ...

随机推荐

  1. unity3d NGUI制作角色展示框

    最近在搞赛车漂移,所以一直没有更新博客 现在已经实现圈数检测.复位点检测.反向检测等功能 本来准备写成三篇文章的,太忙了,等过段时间不忙了在写吧 今天有朋友问我3D角色怎么给他固定在一个框里面 这个功 ...

  2. WDCP安装并配置php5.4和mongodb

    记录一下,免得忘了.全部都是自己测试用过的安装过程没有问题. linux常用命令 mv 移动文件 mkdir 创建文件夹 rm 删除 cp 复制 netstat 网络状态 tar 解压 wget 下载 ...

  3. [C语言]一个很实用的服务端和客户端进行TCP通信的实例

    本文给出一个很实用的服务端和客户端进行TCP通信的小例子.具体实现上非常简单,只是平时编写类似程序,具体步骤经常忘记,还要总是查,暂且将其记下来,方便以后参考. (1)客户端程序,编写一个文件clie ...

  4. 聊聊HTTPS和SSL_TLS协议

    要说清楚 HTTPS 协议的实现原理,至少需要如下几个背景知识. 1. 大致了解几个基本术语(HTTPS.SSL.TLS)的含义 2. 大致了解 HTTP 和 TCP 的关系(尤其是“短连接”VS“长 ...

  5. WPF开发时光之痕日记本——终于完工了。。晒晒截图(三)(已上传安装包)

    由于是业余时间学习的 WPF 的相关开发且不怎么会使用 Blend 软件,所以开发这个客户端着实花费了我很长时间,比如文本编辑器的开发,最初是在 Simple.HtmlEditor 的基础上做的修改, ...

  6. 序列化类型为“System.Data.Entity.DynamicProxies.ActionInfo_”的对象时检测到循环引用。

    解决方案: 加上 db.Configuration.ProxyCreationEnabled = false;这句话搞定~

  7. UIToolbar 如何调整里面的按钮位置

    可以在Bar Button Item后面填个Fixed Space Bar Button Item 或者Flexible Space Bar Button Item,然后再在后面加上下一个Bar Bu ...

  8. python 逐行读取文件的三种方法

    方法一: 复制代码代码如下: f = open("foo.txt")             # 返回一个文件对象  line = f.readline()             ...

  9. 每天一个linux命令(31):grep 命令

    Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来.grep全称是Global Regular Expression Print,表示全局正则表达 ...

  10. android学习——error opening trace file: No such file or directory (2)

    1.疑惑: 程序运行起来的时候日志总是显示下面这个错误,但是不影响程序的正常进行,我是用真机来测试的,android4.4.4(API17). 02-11 14:55:03.629 15525-155 ...