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. 喝咖啡写脚本,顺便再加一点点CSS语法糖 1.选择环境

    经过对前端开发的初步了解,大体上发现了以下几点,前端开发需要使用脚本语言,主要是JavaScript,需要Html,需要CSS,这些东西相信很多人已经很熟了.但是仅仅只是学习一点简单的JS,配合Htm ...

  2. JAVA并发的性能调整

    1.互斥技术 synchronized Lock Atomic 性能比较Atomic >  Lock  > synchronized,当然这不是绝对的.当线程数比较少时,synchroni ...

  3. Javascript基础系列之(六)循环语句(break和continue语句)

    break和continue语句对循环中的代码执行提供了更为严格的流程控制.break语句可以立刻退出循环,阻止再次执行循环体中的任何代码.continue语句只是退出当前这一循环,根据控制表达式还允 ...

  4. Linux下安装项目管理工具Redmine

    http://www.redmine.org.cn/download Linux下安装项目管理工具Redmine1.Ruby安装Ruby on Rails网站推荐使用1.8.7版. 点击(此处)折叠或 ...

  5. hdu2444 判断二分图+最大匹配

    #include<stdio.h> #include<string.h> #include<queue> using namespace std; #define ...

  6. Spring AOP详解 、 JDK动态代理、CGLib动态代理

    AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理以及日志记录.AOP将这些分散在各个业务逻辑中的代码 ...

  7. 【kAri OJ605】陈队的树

    时间限制 1000 ms 内存限制 65536 KB 题目描述 陈队有N棵树,有一天他突然想修剪一下这N棵树,他有M个修剪器,对于每个修剪器给出一个高度h,表示这个修剪器可以把某一棵高度超过h的树修剪 ...

  8. Android4.4中不能发送SD卡就绪广播

    当在Android上进行图片的扫描功能开发时一般会使用:sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse(“file:// ...

  9. BZOJ1083 繁忙的都市

    Description 城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造.城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口 ...

  10. codevs1500 后缀排序

    题目描述 Description 天凯是MIT的新生.Prof. HandsomeG给了他一个长度为n的由小写字母构成的字符串,要求他把该字符串的n个后缀(suffix)从小到大排序. 何谓后缀?假设 ...