前言
又到了一章的总结,这章里的内容。是把我从一个代码的使用者,如何换位成一个代码的编写者。如何让别人用自己的代码更容易,不用去注意太多的无用细节,不用记住冗长的函数名。在使用API时怎样避免使用者会出现理解的偏差。如何处理一些特殊敏感的值,参数如何设置可以更好地自说明,如何减少API对状态的依赖,如何使API更加灵活,更利于用户的编写。下面一一展开介绍,对应的也会说明每条对应希望给到的是哪方面的建议!
第53条:保持一致的约定
个人总结
不要去创造一个独特的API,要使用一些大家惯用的词汇,参数。比如使用统一的命名规范,相似属性设定相同的属性顺序。
命名规范
有好多种,比如:
-
构造函数首字母大写。如MyClass
-
普通函数,原型方法,变量,小写字母开始,其他单词首字母大写。如compareFn,a.setValue(),goodName。
-
常量及全局变量,为全部字母大写。如PI=3.1415
-
私有原型方法及属性,为_开头。如 _good, _getName()
像以上的内容都是在js编写过程中的惯用法,当看到上面的命名时,自然而然会知道表达的是什么意思。
参数的设定
比如我们在使用jquery里的一个函数时,一些函数在传入一个参数,是取值操作。传入二个参数时是付值操作。
如
$('.a').css('width');
$('.a').css('width',100);
$('a').attr('href');
$('a').attr('href','http://wengxuesong.cnblogs.com');
从上面也可以看出,这些函数在第一个参数传入的都是一个可以设定和获取的属性值。而第二个参数都是一个要使用的值。当我们遇到类似的函数时,我们在使用的时候就可以很快使用了。比如
$('.b').data('name');
$('.b').data('name','wxs');
很快就知道上面这个的意思啦~
而且使用的函数都是很容易记忆并符合函数意义的简单词汇。
可以看出,上面这样的一些设定,可以让我们解决前言中提到的一些问题。
如何让别人用自己的代码更容易,不用去注意太多的无用细节,不用记住冗长的函数名
提示
-
在变量命名和函数签名中使用一致的约定
-
不要偏离用户在他们的开发平台中很可能遇到的约定
第54条:将undefined看做“没有值”
个人总结
由于产生undefined的情况很多,所以在对待undefined的时候,想很好地区分它代表的真正的意义并不太容易。
产生undefined值的几种情况
-
变量声明后没有赋值
-
函数参数没有传入真正的实参数,形参的值
-
函数运行没有返回值或直接return时,函数的运行结果的值
-
对象不存在的属性的值
不能把undefined做为函数判断的特殊值进行处理,因为函数无法判定,这个参数是真正传递的为undefined,还是表达式运行的结果。要undefined只做为一个“没有值”来对待。如果要特殊处理一种情况,可以使用选项对象做为参数。
可选参数
-
可选参数在未输入值时是undefined。这时的arguments.length不计入这个参数。
-
当可选参数传入一个值为undefined的值时。这时的arguments.length会计入这个参数。
function Server(port,hostname){
if(arguments.length < 2){
hostname='localhost'
}
hostname=''+hostname;
return 'http://'+hostname+':'+port;
}
var s1=new Server(80,'cnblogs.com');//"http://cnblogs.com:80"var s2=new Server(80);//"http://localhost:80"var s3=new Server(80,undefined);//"http://undefined:80"
使用arguments.length来对函数可选参数进行判断会导致错误。应该对可选参数与undefined来比较来实现功能。
function Server(port,hostname){
if(hostname===undefined){
hostname='localhost'
}
hostname=''+hostname;
return 'http://'+hostname+':'+port;
}
var s1=new Server(80,'cnblogs.com');//"http://cnblogs.com:80"var s2=new Server(80);//"http://localhost:80"var s3=new Server(80,undefined);//"http://localhost:80"
隐式类型转换
在隐式类型转换为真时,undefined会转换为false。但除undefined外还有很多值也可以转换为false,如:null,""(空字符串),0和NaN。
在函数参数中使用真值测试时,设定默认值时。如果上面的0或空字符串为合法值时,就不能用真值来进行判断。
function W(w){
this.w=w||100;
}
var sw=new W(0);
sw.w;//100
上面如果传入0,则得到一个期望宽度为0的对象。
这种情况下必须显示地判断传入的值是否为undefined。
function W(w){
this.w=w===undefined?100:w;
}
var sw=new W(0);
sw.w;//0
这条解决了如何去处理一些特殊敏感的值。
提示
-
避免使用undefined表示任何值
-
使用描述性的字符串值或命名布尔属性的对象,而不要使用undefined或null来代表特定应用标志
-
提供参数默认值应当采用测试undefined的方式,而不是检查arguments.length
-
在允许0、NaN或空字符串为有效参数的地方,绝不要通过真值测试来实现参数默认值
第55条:接收关键字参数的选项对象
个人总结
总结起来就是,当函数接收很多参数时,参数的顺序无法改变(这里也称为位置参数),导致有些不需要的值也得传入,没法处理多个参数可选的情况。比如
function aaa(a,b,c){
//b,c都是可选的
}
aaa('a',,'c')
如上,如果我b不想传递只想传入a,c也必须传入把b传递进去。要不里面的代码就会出错。
上面的代码也记忆也是一种挑战,里面的每个参数应该对应什么样的值。
面对这种情况,可以选择使用选项对象。
选项对象
使用选项对象,选项对象里的属性名可以很好说明参数的作用。选项对象参数,只处理非必选的参数。对于非必选参数,可以提供一些默认值。
-
如果仅包括可选参数。可能会省略掉所有的参数,选项对象包括所有参数。
-
如果有一个或两个必选参数,使它们独立于选项对象。
使用每一项值的对于选项对象的默认值的设定。需要检测所有的非必选的参数项值。这个工作并不好做,需要去处理真值测试,或排除合理值的情况等问题。这样做起来比使用位置参数的方法更加烦琐。可以使用对象的覆盖处理方法,把对象覆盖的处理抽象出来,然后调用,这样就可以使代码的逻辑更加清晰。比如
function A(parent,message,opts){
opts=extend({
320,
height:240
});
opts=extend({
x:10,
y:10,
title:'alert',
modal:false
},opts);
extend(this,opts);
}
抽象出来的扩展函数为
function extend(target,source){
if(source){
for(var key in source){
var val=source[key];
if(typeof val !== 'undefined'){
target[key]=val;
}
}
}
return target;
}
一致性是库设计的目标,可以给API的使用者更好地预测它的功能及使用方法。
这条解决了使用API时怎样避免使用者会出现理解的偏差。如何处理一些特殊敏感的值,参数如何设置可以更好地自说明。
提示
-
使用选项对象使得API更具有可读性、更容易记忆
-
所有通过选项对象提供的参数应当被视为可选的
-
使用extend函数抽象出从选项对象中提取值的逻辑
第56条:避免不必要的状态
个人总结
API分为两类:有状态的和无状态的。无状态的API相当于纯函数,行为只取决于输入,与程序所处的环境状态无关。有状态的方法,对于同一个方法可能返回不同的结果。主要取决于方法所处的状态。
无状态的API更容易学习和使用,因为所有的行为都是函数本身决定的,只要查询相关函数的代码就能知道输出是否正确。
有状态的API处于不同的状态有可能会产生不同的结果。导致在记忆和使用时都要记住额外的信息。状态的改变也常常会带来代码的耦合度更高。无状态的API可以使代码更加模块化,避免与其它代码产生耦合。
在设计API时,无状态的API更好。
提示
-
尽可能地使用无状态的API
-
如果API是有状态的,标示出每个操作与哪些状态有关联
第57条:使用结构类型设计灵活的接口
个人总结
结构类型(鸭子类型):任何对象只要具有预期的结构就属于该类型。这个类似于强类型面向对象语言里说的接口,也就是面向接口编程。
结构类型可以有利于单元测试,可以很容易去实现一个测试的数据结构。
结构类型也可以使代码各部分解耦,代码的依赖只是通过结构类型。
在实现代码时,不用去管结构类型最终的实现细节,只要提供对应的方法及属性,那么程序就可以正常运行。
提示
-
使用结构类型来设计灵活的对象接口
-
结构接口更灵活、更轻量,所以应该避免使用继承
-
针对单元测试,使用mock对象即接口的替代实现来提供可复验的行为
第58条:区分数组对象和类数组对象
个人总结
分离数组对象
数组对象和类数组对象,使用类型判断可以直接区分。
var a=[];
var b={0:1,length:1};
var toString=({}).toString;
toString.call(a);//"[object Array]"
toString.call(b);//"[object Object]"
这样一目了然。但这与js的灵活的类数组对象的概念有争执,因为任何对象都可被视为数组,只要它遵循正确的接口。上一条使用结构类型设计灵活的接口。灵活的结构只要一个数据符合相应的接口,就可以把它视为正确的数据。
重载
重载两种类型意味着必须有一种方法来区分两种不同情况。如果出现了两种情况的重叠区域,则无法对API进行重载。
API绝不应该重载与其他类型有重叠的类型。
Array.isArray函数
这个函数测试一个值是否是数组,而不管原型继承。
var a={};
var b=[];
var c=10;
Array.isArray(a);//falseArray.isArray(b);//trueArray.isArray(c);//false
可以直接区分,数组与类数组。
类数组转化为数组
var slice=[].slice;
var b={0:10,1:'good',length:2};
slice.call(b);//[10,'good']
如果对API的传入参数有特殊指定要求的,需要在文档中注明,API的重载也必须要注明,不同情况的参数要求。
提示
-
绝不重载与其他类型有重叠的结构类型
-
当重载一个结构类型与其他类型时,先测试其他类型
-
当重载其他对象类型时,接收真数组而不是类数组对象
-
文档标注你的API是否接收真数组或类数组值
-
使用ES5提供的Array.isArray方法测试真数组
第59条:避免过度的强制转换
个人总结
重载和强制转换
重载基于类型的判断,而强制转换使参数类型信息丢失,导致结果和预期不同。
在使用参数类型来作为重载依据时,应该避免强制转换。
重载时通过对参数的类型进行强制要求,来实现API的设计,这样代码更谨慎。
防御性编程
以额外的检查来抵御潜在的错误。抵御所有的错误是不可能的。除js中提供的基本检查工具外,可以通过编写一些简洁的检查工具函数来辅助开发。
额外的代码会影响程序的性能,也可以更早地捕获错误。看具体情况来使用防御性编程。
提示
-
避免强制转换和重载的混用
-
考虑防御性地监视非预期的输入
第60条:支持方法链
个人总结
js里自带的方法链式调用,如字符串的replace方法
function escapeBasicHTML(str){
return str.replace(/&/g,"&")
.replace(/< /g,"<")
.replace(/>/g,">")
.replace(/"/g,""")
.replace(/'/g,"'");
}
数组方法
var users=records.map(function(record){
return record.username;
})
.filter(function(username){
return !!username;
})
.map(function(username){
return username.toLowerCase();
});
这些都可以写成传统的方式,把每次的返回值保存到中间变量。
实现方法链的关键是,每次都返回下一个方法的对象。
无状态的API中,可以返回一个新对象,则链式得到了自然的结果。像上面的replace方法,返回的是一个新的字符串对象。
有状态的API也值得使用,这时方法链被称为流畅式。这个好举例子,如jQuery里的方法操作。
$('body').html('good').addClass('y');
方法链的书写风格,需要代码进行一定的处理才能支持(返回对象自身)。方法链的代码都可以改写成传统的风格。
提示
-
使用方法链来连接无状态的操作
-
通过在无状态的方法中返回新对象来支持方法链
-
通过在有状态的方法中返回this来支持方法链
总结
纵观这一章,从比写具体代码更高的一个层次给了指导。前前后后,每条都提出了,文档很重要。有特殊要求和惯用法不同的都要文档说明。
-
在处理API接口命名时,保持命名约定一致,不偏离用户习惯。
-
处理undefined时要特别对象,不能简单处理。
-
参数使用选项参数,说明性更强,更容易记忆和使用。
-
设计无状态的API接口,更容易模块化和使用。
-
面向结构类型编程,API更灵活。
-
函数重载要注意不同情况,不能有重叠区域。
-
强制转换会破坏依赖类型判断的重载。
-
支持方法链,使代码书写起来更流畅。
倒数第二章了,还有一章就要完事了。这一章的内容虽然硬知识点没有多少,但我看起来是这几章里就吃力的,因为找不到着力点。编码的多少直接就决定了对这章各条的理解程度,这章可以在写一些代码后回来再反复看一下,掌握后就内化为自己的能力啦。