對於前端開發而言,確定會和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的一些我的建議,就到這裏了,若是之後有更好的想法,會第一時間分享,和你們交流意見。若是你們有什麼想法,歡迎指點迷津。
---------------------------華麗的分割線--------------------------------
想了解更多,關注個人微信公衆號-守候書閣