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. 服务发现:Zookeeper vs etcd vs Consul

    [编者的话]本文对比了Zookeeper.etcd和Consul三种服务发现工具,探讨了最佳的服务发现解决方案,仅供参考. 如果使用预定义的端口,服务越多,发生冲突的可能性越大,毕竟,不可能有两个服务 ...

  2. linux php配置ftp扩展

    linux+nginx+php+mysql环境下,在部署的时候没有添加php的ftp扩展. 1.找到安装的PHP源码包解压的文件夹进入到到FTP的扩展目录# /root/php-5.3.6/ext/f ...

  3. Mysql 调优小技巧

    MySQL是一个功能强大的开源数据库.随着越来越多的数据库驱动的应用程序,人们一直在推动MySQL发展到它的极限.这里是101条调节和优化MySQL安装的技巧.一些技巧是针对特定的安装环境的,但这些思 ...

  4. 为HTML添加图片登录按钮

    来源于:http://www.2cto.com/kf/201510/447673.html <!DOCTYPE html> <html> <head lang=" ...

  5. grid-css

    .fil-container { width: 100%; max-width: 75rem; margin-right: auto; margin-left: auto; padding-left: ...

  6. c++ 中 delete p与 delete []p的区别

    #include <cstdio> class A{private: int i;public: ~A() { printf("hi"); }};void d(A *) ...

  7. Fluent Ribbon项目出现“命名空间“clr-namespace:Fluent;assembly=Fluent”中不存在“RibbonWindow”名称”的解决方法

    之前在学习@aganqin的Fluent Ribbon项目(http://www.cnblogs.com/aganqin/p/3269384.html).但是一直都有引用了Fluent.dll后仍旧出 ...

  8. note.js之 Mongodb在Nodejs上的配置及session会话机制的实现

    上篇我们使用nodejs实现了一个express4的网站构建配置,但一个有面的网站怎么可以缺少一个数据库呢.现在较为流行的就是使用MONGODB来作为nodejs网站引用的数据库,可能它与nodejs ...

  9. 【HDU 5578】Friendship of Frog

    题 题意 求相同字母最近距离 分析 用数组保存各个字母最后出现的位置,维护最小距离. 代码 #include <cstdio> int c[30],n,p,a,minl; char ch; ...

  10. Mac OS X系统下编译运行C代码

    1.使用编译器将源文件中的代码转换为二进制代码,这个过程叫做编译. 将终端的工作路径切换到源文件所在的路径. cc -c 源文件的名称.例如:cc -c main.c 如果没有意外的话,就会在当前工作 ...