对于前端开发而言,确定会和API打交道,你们也都会想过怎么设计本身的API。优秀的 API 之于代码,就如良好内涵对于每一个人。好的 API 不但利于使用者理解,开发时也会事半功倍,后期维护更是顺风顺水。至于怎么设计API,今天就提下我本身的一些建议。若是你们有什么好的想法,欢迎指点。css
良好的一个命名习惯,就是效率开发的第一步。若是命名规范,对本身而言,文件整理有很大的帮助,后期修改文件、能够快速的定位文件,命名规范,也显得本身专业。对团队而言,若是有统一的规范命名,交接时能够减小大量的学习和沟通成本。html
关于命名,下面提点几个小建议前端
这个应该说是命名的一个底线了,常常性出现,单词拼写错误,搞得本身或者团队的人都一头雾水的状况再也不少数。我遇到状况比较深入的有vue
中文大意 | 指望 | 实际 |
---|---|---|
表单 | form | from |
报名 | sign-up | sign-in |
采纳 | adopt | adept |
内容 | content | contend |
测试 | test | text |
联系 | contact | contract |
高度 | height | heigth |
宽度 | width | widht |
移动 | mobile | moblie |
标签 | tab | tap |
这些单词,若是是拼写错误还好,至少编辑器都会提醒。可是若是写错了,可是单词又是正确的单词就可大可小了(表单,报名,采纳,内容这些例子,单词写错了,意思变了,可是单词是正确的,编辑器都不会提醒)。react
试过挖坑比较深的一次就是:一个活动,有报名,有签到的功能!处理方法以下es6
获取已报名用户信息:getSignUpUserList,
重置报名的数据:resetSignUpUser,
提交报名操做:signInDo面试
获取已签到用户信息:getSignInUserList,
重置签到的表单数据:resetSignInUser,
提交签到的操做:signUpDojson
修改bug的时候,彻底懵逼了,缘由你们懂的。设计模式
全部涉及遍历,操做对象,数组,集合的函数,建议都采用复数。数组
对于展示复数,不一样公司有不一样的习惯,可是得统一,好比产品列表-productList
。这里用了list表示复数,再其它地方,就不建议使用products
这种方式表示复数了
这个主要的两方面的内容
好比弹窗上面的信息,有些时候见到,使用包含notice
的字样,可是实际上,notice
的中文意思,准确的应该是‘公告,告示,声明’之类。
一个弹窗这样的会话消息,建议使用message
这个字样。notice
应该像‘公告,告示,声明’之类的状况使用。
好比关闭弹窗的方法,的方法是closeDialog
,而后显示弹窗用的又是showDialog
。show
的意思是‘显示’,反义词应该是hide
‘隐藏’。而close
意思是关闭,反义词应该是open
。
附经常使用反义词组(有些带缩写)
in | out |
on | off |
prev | next |
show | hide |
close | open |
success | fail |
before | after |
begin | end |
这一块,原本打算放在2-2里面讲的,由于命名若是有意义也是一个底线。可是最后放在这里,是由于这个状况在函数里面出现得很少,更多应该出如今普通变量里面(相信不少人会遇到过这样的命名:var n1,n2,n3;)。关于命名,仍是建议你们要起有意义名称,不使用没意义的命名。
遇到最多的状况,就是图标的命名方面。
好比下面的图标(选自某平台的底部导航栏),点击不一样的图标出发不一样的方法。
不少人喜欢下面的命名
//版本1 function handle1(){ } function handle2(){ } //版本2 function handleA(){ } function handleB(){ } //版本3 function handleOne(){ } function handleTwo(){ }
这样的命名,别人函数了,就算是元素的 class 。这样的命名在后期维护绝对增长了难度。甚至可能致使重构。
建议的姿式
function handleHome(){ } function handleCollect(){ }
文章说的API,主要针对的是函数,可是在这一小块里面,也列举一下其它的目标的建议命名方式。
待命名对象 | 推荐名称 |
---|---|
图片 | ‘-’ ‘_’ 分割 |
class,id | ‘-’ 分割 |
文件,变量 | 驼峰命名 |
临时变量 | ‘_’ 开头,驼峰命名 |
对于中文拼音,应该说只有一种状况,被中国人创造出来,没有英文翻译的。
命名 | 含义 |
---|---|
taobao | 淘宝 |
微博 | |
zongzi | 粽子 |
pinyin | 拼音 |
在一年多之前,遇到一个中二的命名-dengluDo。当时一直不知道是什么玩意,后来向那我的打听才知道,是执行登陆的操做,denglu是中文拼音,do又是英文,这样的命名。后期若是维护,他不哭,算我输。
有些状况,给特定的对象命名,还要用特定的名字,能够说是潜规则吧。印象最清楚的就是给按钮命名要么全拼,要么写btn。很清楚的记得我一个老师说过:写but,bto的程序也能正常运行,也没人说你错,可是我作面试官,就是不录用你,就说你不专业。
待命名对象 | 推荐名称 | 错误示范 |
---|---|---|
按钮 | btn | but bto |
背景 | bg | back background |
模板 | tpl | tem |
提示信息 | msg | mes |
标签栏 | tab | tit |
网站大图(广告宣传图) | banner | ban |
注册 | register | sign-in |
对于函数而言,参数是用户设置最频繁,也是最关心的部分,合理设计函数参数,这一步很重要,直接影响函数的使用。
这个应该说是一个习惯吧,不要直接改变入参的值。这个规则的初衷是解决函数反作用问题。若是参数是一个引用类型的数据,若是在函数内修改了参数,到时候将会使得本来的数据发生改变,每每会发生难以追踪的问题。
参数的数量,我的建议就是,超过3个,使用对象进行封装。由于若是API参数越多,那么使用对于这个API的记忆成本就越大,易用性也很受影响。
好比下面的例子:
encryptStr: function (str, regArr, type, replacement) { var regtext = '', Reg = null, _type=type||0, replaceText = replacement || '*'; //ecDo.encryptStr('18819322663',[3,5,3],0) //result:188*****663 //repeatStr是在上面定义过的(字符串循环复制),你们注意哦 if (regArr.length === 3 && type === 0) { regtext = '(\\w{' + regArr[0] + '})\\w{' + regArr[1] + '}(\\w{' + regArr[2] + '})' Reg = new RegExp(regtext); var replaceCount = this.repeatStr(replaceText, regArr[1]); return str.replace(Reg, '$1' + replaceCount + '$2') } //ecDo.encryptStr('asdasdasdaa',[3,5,3],1) //result:***asdas*** else if (regArr.length === 3 && type === 1) { regtext = '\\w{' + regArr[0] + '}(\\w{' + regArr[1] + '})\\w{' + regArr[2] + '}' Reg = new RegExp(regtext); var replaceCount1 = this.repeatStr(replaceText, regArr[0]); var replaceCount2 = this.repeatStr(replaceText, regArr[2]); return str.replace(Reg, replaceCount1 + '$1' + replaceCount2) } //ecDo.encryptStr('1asd88465asdwqe3',[5],0) //result:*****8465asdwqe3 else if (regArr.length === 1 && type === 0) { regtext = '(^\\w{' + regArr[0] + '})' Reg = new RegExp(regtext); var replaceCount = this.repeatStr(replaceText, regArr[0]); return str.replace(Reg, replaceCount) } //ecDo.encryptStr('1asd88465asdwqe3',[5],1,'+') //result:"1asd88465as+++++" else if (regArr.length === 1 && type === 1) { regtext = '(\\w{' + regArr[0] + '}$)' Reg = new RegExp(regtext); var replaceCount = this.repeatStr(replaceText, regArr[0]); return str.replace(Reg, replaceCount) } }
你们能够看上面的注释,就知道这段代码的具体做用了,若是想一想就找个参数,我必需要除了记得4个参数的做用,还要记得参数的顺序。
若是使用对象记录参数,用户只须要记得4个参数的做用,不须要记参数的顺序。
encryptStr: function (obj) { var _default={ type:0, replacement:'*' }; for(var key in obj){ _default[key]=obj[key]; } }, //调用方式 ecDo.encryptStr({str:'18819266335',regArr:[5],type:0,replacement:'-'});
这样还有一个好处就是,好比像刚才的函数,type这个参数,我想保留默认值,偷懒不传。原来的方案,就得这样传。
ecDo.encryptStr('1asd88465asdwqe3',[5],'','+');
这样确定是会激起很多有代码洁癖的开发者,好比我。若是使用对象,就很好避免了。
ecDo.encryptStr({str:'18819266335',regArr:[5],replacement:'-'});
这个应该没什么可能,就一个意思:必填重要的参数前置,可省略的参数后置。
好比下面的例子
/格式化处理字符串 //ecDo.formatText('1234asda567asd890') //result:"12,34a,sda,567,asd,890" //ecDo.formatText('1234asda567asd890',4,' ') //result:"1 234a sda5 67as d890" //ecDo.formatText('1234asda567asd890',4,'-') //result:"1-234a-sda5-67as-d890" formatText: function (str, size, delimiter) { var _size = size || 3, _delimiter = delimiter || ','; var regText = '\\B(?=(\\w{' + _size + '})+(?!\\w))'; var reg = new RegExp(regText, 'g'); return str.replace(reg, _delimiter); },
调用你们都看得出来。若是API这样设计
formatText: function (size, delimiter, str) { var _size = size || 3, _delimiter = delimiter || ','; var regText = '\\B(?=(\\w{' + _size + '})+(?!\\w))'; var reg = new RegExp(regText, 'g'); return str.replace(reg, _delimiter); },
就得这样调用,若是这样写API,被批斗的可能性很大!
ecDo.formatText('','','1234asda567asd890')
好比这个例子,页面有这样的元素
<div class="div1"></div> <div class="div1"></div> <div id="div2"></div>
有一个相似jQuery的css这个API的API。
css: function (dom, json) { for (var attr in json) { dom.style[attr] = json[attr]; } }
而后给这些div设置样式的时候,代码以下
var oDiv1 =document.querySelectorAll(".div1"); var oDiv2=document.querySelector("#div1"); ecDo.css(oDiv2,{'height':'100px','width':'100px','background':'#333'}); ecDo.css(oDiv1,{'height':'100px','width':'100px','background':'#09f'});
当运行到ecDo.css(oDiv1,{'height':'100px','width':'100px','background':'#09f'});
会提示报错,缘由你们也知道。css这个API里面,只处理了单个元素,并无处理元素的集合。
建议的方式是把 css 这个API改为可批量处理元素集合的。
css: function (dom, json) { if (dom.length) { for (var i = 0; i < dom.length; i++) { for (var attr in json) { dom[i].style[attr] = json[attr]; } } } else { for (var attr in json) { dom.style[attr] = json[attr]; } } },
一个相似jQuery的html这个API的API-html
以前遇到一个开发者的处理方式是:获取元素的innerHTML和设置元素innerHTML分开为两个方法-getHtml,setHtml。这样的问题又在于记忆的成本比原生的 innerHTML 还要高。建议的姿式就是,获取和设置用同一个API。
html: function (dom) { if (arguments.length === 1) { return dom.innerHTML; } else if (arguments.length === 2) { dom.innerHTML = arguments[1]; } } ecDo.html(oDiv);//获取 ecDo.html(oDiv,'守候');//设置
可扩展性,就是建议遵照开放-封闭原则。对扩展开放,对修改关闭。好比jQuery的$.fn和$.fn.extend()。
说一个简单的例子-计算加薪额度
var addMoney = (function () { //定义策略类 var strategies = { A:function(money){ return money + 2000; }, B:function(money){ return money + 1000; } }; //暴露接口 return { //根据等级和现工资,输入加薪后的工资 compute:function(lv,money){ return strategies[lv](money) } }; })(); //好比:等级为A,5000+2000 console.log(addMoney.compute('A',5000))//7000 //好比:等级为B,20000+1000 console.log(addMoney.compute('B',20000))//21000
代码看着没有问题,可是若是之后需求要增长C等级呢?这就不得不修改strategies。在里面增长方法。
以下
var strategies = { A:function(money){ return money + 2000; }, B:function(money){ return money + 1000; }, C:function(money){ return money + 500; } };
这样实现也简单,若是之后要增长S等级呢?又得改strategies。这里还有一个问题就是,若是增长的C等级只有在A模块须要用到,在B模块不会出现,那么在B模块引用addMoney的时候,又会把C等级的计算方式也引入进去,形成没必要要的资源浪费。
建议的方式是,设置一个接口,扩展strategies。
var addMoney = (function () { //定义策略类 let strategies = { A:function(money){ return money + 2000; }, B:function(money){ return money + 1000; } }; //暴露接口 return { //根据等级和现工资,输入加薪后的工资 compute:function(lv,money){ return strategies[lv](money) }, //扩展等级 addRule:function(lv,fn){ strategies[lv]=fn; } }; })(); //增长C等级的调用 addMoney.addRule('C',function(money){ return money + 500; }); console.log(addMoney.compute('C',20000))//20500
函数的反作用,相信不少人都会遇到过,好比在一个函数体内修改一个外部做用域的变量,或者全局变量,在函数体内修改引用类型的参数,这些状况多少都会遇到过。
如何避免呢?主要是如下两个写代码习惯。
1.函数体内可使用参数,进行操做,可是不能修改。若是修改,用一个临时变量记录参数(若是是引用类型,须要用深拷贝记录)。这样能够避免直接修改参数。
2.对于函数外的变量,如全局变量。函数体内能够访问,可是不能修改。
3.若是须要给函数外的变量赋值,不能在函数体内操做,把值返回到外部,在外部进行赋值。(感受这里有点啰嗦,由于赋值了,就是修改了外部变量,就违反了第二点)。
//很差作法 var myName=''; function setName(firstName,lastName){ myName=firstName+lastName; } setName('守','侯'); //推荐作法 var myName=''; function setName(firstName,lastName){ return firstName+lastName; } myName=setName('守','侯');
这个建议主要就是为了兼顾之前的写法。仍是拿上面的那个例子吧!
本来传参方式是这样
encryptStr: function (str, regArr, type, replacement) {};
后来升级改为这样
encryptStr: function (obj){}
这样问题就来了,一个项目里面,由于历史的缘由不免会使用这个API,而且使用了第一种方式传参。如今API改了,解决的方案有两个,要么把整个项目使用的这个API的方式,都改为第二种的传参方式,要么就是对接口进行向下兼容,兼容之前的方案。
encryptStr: function (obj) { var _default={ type:0, replacement:'*' }; //若是仍是以以前的方式调用函数,兼容性判断 if(arguments.length>1){ _default.str=arguments[0]; _default.regArr=arguments[1]; _default.type=arguments[2]||0; _default.replacement=arguments[3]||'*'; } else{ for(var key in obj){ _default[key]=obj[key]; } } //下面代码略 },
若是API已经准备来一个大版本的更新,(好比从1.0.0升级到2.0.0,不是1.0.0升级到1.0.1,或者1.0.0升级到1.1.0)。不打算兼容之前的版本了。能够忽略这一步,毕竟兼容性的代码可能也不少。
这一步能够说是API设计最高级的一步,也是最难开发的一步,这就是为何这篇文章会带有‘大道至简’的字样,即便API的实现很难,但使用起来简单感受就是高级的API。这一步也直接影响API的好用与否。简单的API不可是用起来简单,试试能够一看就懂的API。这样的API更易理解、记忆、调试和变动使用方式。
原生的API,好比Date,some、map、find等全部数组遍历操做函数,es6提供的Object.assign,Object.keys,Object.values等。
曾经的霸主jQuery,如今的王者react,黑马vue。这些项目让人拍手称赞的缘由虽然有不少,但也不能否认的,那即是它们的API设计很是的巧妙。如:jQuery的$,siblings,toogleClass,animate等,react的cloneElement,replaceProps等,vue的nextTick,set等。
jQuery对于如今而言,虽然是过期了,但里面的知识仍是值得学习,好比使用的淋漓尽致的 js 写做技巧,设计模式,以及 API 设计等。
本身写的API,我也是把API写得尽可能的简单,最高境界就是让别人扫一眼文档,就知道记牢了API的使用方式。这个是我追求的目标,只是如今距离仍是有点远。你们看我encryptStr
这个API就知道(此处尴尬一天)。
在个人眼里,一个好的API,会有一个一看就懂的名字,一个强大的功能,一个简单的调用方式。虽然只有三个条件,可是这三个条件结合起来,可不是那么容易作到的。一个好的API,不管是对本身,对团队,对项目开发都是一个很好的帮助。
对于设计API的一些我的建议,就到这里了,若是之后有更好的想法,会第一时间分享,和你们交流意见。若是你们有什么想法,欢迎指点迷津。
---------------------------华丽的分割线--------------------------------
想了解更多,关注个人微信公众号-守候书阁