1.前言
對於前端開發而言,肯定會和API打交道,大家也都會想過怎麼設計自己的API。優秀的 API 之於程式碼,就如良好內涵對於每個人。好的 API 不但利於使用者理解,開發時也會事半功倍,後期維護更是順風順水。至於怎麼設計API,今天就提下我自己的一些建議。如果大家有什麼好的想法,歡迎指點。
2.命名
良好的一個命名習慣,就是效率開發的第一步。如果命名規範,對自己而言,檔案整理有很大的幫助,後期修改檔案、可以快速的定位檔案,命名規範,也顯得自己專業。對團隊而言,如果有統一的規範命名,交接時可以減少大量的學習和溝通成本。
關於命名,下面提點幾個小建議
2-1.正確拼寫
這個應該說是命名的一個底線了,經常性出現,單詞拼寫錯誤,搞得自己或者團隊的人都一頭霧水的情況不再少數。我遇到情況比較深刻的有
中文大意 | 期望 | 實際 |
---|---|---|
表單 | form | from |
報名 | sign-up | sign-in |
採納 | adopt | adept |
內容 | content | contend |
測試 | test | text |
聯絡 | contact | contract |
高度 | height | heigth |
寬度 | width | widht |
移動 | mobile | moblie |
標籤 | tab | tap |
這些單詞,如果是拼寫錯誤還好,至少編輯器都會提醒。但是如果寫錯了,但是單詞又是正確的單詞就可大可小了(表單,報名,採納,內容這些例子,單詞寫錯了,意思變了,但是單詞是正確的,編輯器都不會提醒)。
試過挖坑比較深的一次就是:一個活動,有報名,有簽到的功能!處理方法如下
獲取已報名使用者資訊:getSignUpUserList,
重置報名的資料:resetSignUpUser,
提交報名操作:signInDo
獲取已簽到使用者資訊:getSignInUserList,
重置簽到的表單資料:resetSignInUser,
提交簽到的操作:signUpDo
修改bug的時候,完全懵逼了,原因大家懂的。
2-2.注意單複數
所有涉及遍歷,操作物件,陣列,集合的函式,建議都採用複數。
對於展現複數,不同公司有不同的習慣,但是得統一,比如產品列表-
productList
。這裡用了list表示複數,再其它地方,就不建議使用products
這種方式表示複數了
2-3.用詞準確
這個主要的兩方面的內容
2-3-1.單詞意思搞錯
比如彈窗上面的資訊,有些時候見到,使用包含notice
的字樣,但是實際上,notice
的中文意思,準確的應該是‘公告,告示,宣告’之類。
一個彈窗這樣的會話訊息,建議使用message
這個字樣。notice
應該像‘公告,告示,宣告’之類的情況使用。
2-3-2.正反詞義單詞錯用
比如關閉彈窗的方法,的方法是closeDialog
,然後顯示彈窗用的又是showDialog
。show
的意思是‘顯示’,反義詞應該是hide
‘隱藏’。而close
意思是關閉,反義詞應該是open
。
附常用反義片語(有些帶縮寫)
in | out |
on | off |
prev | next |
show | hide |
close | open |
success | fail |
before | after |
begin | end |
2-4.命名意義
這一塊,本來打算放在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(){
}
複製程式碼
2-5.命名格式
文章說的API,主要針對的是函式,但是在這一小塊裡面,也列舉一下其它的目標的建議命名方式。
待命名物件 | 推薦名稱 |
---|---|
圖片 | ‘-’ ‘_’ 分割 |
class,id | ‘-’ 分割 |
檔案,變數 | 駝峰命名 |
臨時變數 | ‘_’ 開頭,駝峰命名 |
2-6.處理中文拼音
對於中文拼音,應該說只有一種情況,被中國人創造出來,沒有英文翻譯的。
命名 | 含義 |
---|---|
taobao | 淘寶 |
微博 | |
zongzi | 粽子 |
pinyin | 拼音 |
在一年多以前,遇到一箇中二的命名-dengluDo。當時一直不知道是什麼玩意,後來向那個人打聽才知道,是執行登入的操作,denglu是中文拼音,do又是英文,這樣的命名。後期如果維護,他不哭,算我輸。
2-7.命名潛規則
有些情況,給特定的物件命名,還要用特定的名字,可以說是潛規則吧。印象最清楚的就是給按鈕命名要麼全拼,要麼寫btn。很清楚的記得我一個老師說過:寫but,bto的程式也能正常執行,也沒人說你錯,但是我做面試官,就是不錄用你,就說你不專業。
待命名物件 | 推薦名稱 | 錯誤示範 |
---|---|---|
按鈕 | btn | but bto |
背景 | bg | back background |
模板 | tpl | tem |
提示資訊 | msg | mes |
標籤欄 | tab | tit |
網站大圖(廣告宣傳圖) | banner | ban |
註冊 | register | sign-in |
3.引數
對於函式而言,引數是使用者設定最頻繁,也是最關心的部分,合理設計函式引數,這一步很重要,直接影響函式的使用。
3-1.const入參
這個應該說是一個習慣吧,不要直接改變入參的值。這個規則的初衷是解決函式副作用問題。如果引數是一個引用型別的資料,如果在函式內修改了引數,到時候將會使得原本的資料發生改變,往往會發生難以追蹤的問題。
3-2.控制引數數量
引數的數量,個人建議就是,超過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:`-`});
複製程式碼
3-3.前置相關性高的引數
這個應該沒什麼可能,就一個意思:必填重要的引數前置,可省略的引數後置。
比如下面的例子
/格式化處理字串
//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`)
複製程式碼
4.作用
4-1.支援批量處理
比如這個例子,頁面有這樣的元素
<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];
}
}
},
複製程式碼
4-2.多型處理
一個類似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,`守候`);//設定
複製程式碼
4-3.可擴充套件性
可擴充套件性,就是建議遵守開放-封閉原則。對擴充套件開放,對修改關閉。比如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 複製程式碼
4-4.避免副作用
函式的副作用,相信很多人都會遇到過,比如在一個函式體內修改一個外部作用域的變數,或者全域性變數,在函式體內修改引用型別的引數,這些情況多少都會遇到過。
如何避免呢?主要是以下兩個寫程式碼習慣。
1.函式體內可以使用引數,進行操作,但是不能修改。如果修改,用一個臨時變數記錄引數(如果是引用型別,需要用深拷貝記錄)。這樣可以避免直接修改引數。
2.對於函式外的變數,如全域性變數。函式體內可以訪問,但是不能修改。
3.如果需要給函式外的變數賦值,不能在函式體內操作,把值返回到外部,在外部進行賦值。(感覺這裡有點囉嗦,因為賦值了,就是修改了外部變數,就違反了第二點)。
//不好做法
var myName=``;
function setName(firstName,lastName){
myName=firstName+lastName;
}
setName(`守`,`侯`);
//推薦做法
var myName=``;
function setName(firstName,lastName){
return firstName+lastName;
}
myName=setName(`守`,`侯`);
複製程式碼
5.向下相容
這個建議主要就是為了兼顧以前的寫法。還是拿上面的那個例子吧!
原本傳參方式是這樣
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)。不打算相容以前的版本了。可以忽略這一步,畢竟相容性的程式碼可能也很多。
6.簡單
這一步可以說是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就知道(此處尷尬一天)。
7.小結
在我的眼裡,一個好的API,會有一個一看就懂的名字,一個強大的功能,一個簡單的呼叫方式。雖然只有三個條件,但是這三個條件結合起來,可不是那麼容易做到的。一個好的API,無論是對自己,對團隊,對專案開發都是一個很好的幫助。
對於設計API的一些個人建議,就到這裡了,如果以後有更好的想法,會第一時間分享,和大家交流意見。如果大家有什麼想法,歡迎指點迷津。
—————————華麗的分割線——————————–
想了解更多,關注我的微信公眾號-守候書閣