有時候,我會想:比我優秀的人,比我更努力。我努力有什麼用。可是如今我習慣反過來想這句話,別人爲何會比我優秀,就是由於別人比我更努力。與其拼天賦,更不如比行動。git
最近有幾天時間空閒,也是在學怎麼寫更有可讀性的代碼,更簡單,方便的API。簡單來講就是重構方面的內容。今天簡單分享下,對之前一個小項目(ecDo,歡迎你們star)的API重構方式,下面的的代碼如無說明,都是選取自個人項目中這個文件:ec-do-3.0.0-beta.1.js 中的 ecDo 這個對象(針對不一樣的重構目的,只列舉1-3個表明實例,不一一列出)。若是你們有什麼更好的方式,也歡迎在評論區留下您的建議。github
首先說明一點,重構你們不要爲重構而重構,要有目的重構。下面的改動,都是針對我原來的實現方式,更換更好的實現方式。主要會涉及在平常開發上,頻繁使用的三個設計原則(單一職責原則,開放-封閉原則,最少知識原則),關於API設計的原則,不止三個。還有裏式替換原則,依賴倒置原則等,可是這幾個平常開發上沒有感受出來,因此這裏就很少說了。 而後就是,雖然這幾個帶有‘原則’的字樣,可是這些原則只是一個建議,指導的做用,沒有哪一個原則是必需要遵照的,在開發上,是否應該,須要遵照這些原則,具體狀況,具體分析。數組
這部份內容,主要就是有些函數,違反了單一職責原則。這樣潛在的問題,可能會形成函數巨大,邏輯混亂,致使代碼難以維護等。bash
在之前的版本,對這個函數的定義是:返回數組(字符串)出現最多的幾回元素和出現次數。微信
原來實現的方案cookie
/**
* @description 降序返回數組(字符串)每一個元素的出現次數
* @param arr 待處理數組
* @param rank 長度 (默認數組長度)
* @param ranktype 排序方式(默認降序)
*/
getCount(arr, rank, ranktype) {
let obj = {}, k, arr1 = []
//記錄每一元素出現的次數
for (let i = 0, len = arr.length; i < len; i++) {
k = arr[i];
if (obj[k]) {
obj[k]++;
} else {
obj[k] = 1;
}
}
//保存結果{el-'元素',count-出現次數}
for (let o in obj) {
arr1.push({el: o, count: obj[o]});
}
//排序(降序)
arr1.sort(function (n1, n2) {
return n2.count - n1.count
});
//若是ranktype爲1,則爲升序,反轉數組
if (ranktype === 1) {
arr1 = arr1.reverse();
}
let rank1 = rank || arr1.length;
return arr1.slice(0, rank1);
},
複製代碼
調用方式dom
//返回值:el->元素,count->次數
ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2])
//默認狀況,返回全部元素出現的次數
//result:[{"el":"2","count":6},{"el":"1","count":4},{"el":"3","count":2},{"el":"4","count":1},{"el":"5","count":1},{"el":"6","count":1}]
ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3)
//傳參(rank=3),只返回出現次數排序前三的
//result:[{"el":"2","count":6},{"el":"1","count":4},{"el":"3","count":2}]
ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],null,1)
//傳參(ranktype=1,rank=null),升序返回全部元素出現次數
//result:[{"el":"6","count":1},{"el":"5","count":1},{"el":"4","count":1},{"el":"3","count":2},{"el":"1","count":4},{"el":"2","count":6}]
ecDo.getCount([1,2,3,1,2,5,2,4,1,2,6,2,1,3,2],3,1)
//傳參(rank=3,ranktype=1),只返回出現次數排序(升序)前三的
//result:[{"el":"6","count":1},{"el":"5","count":1},{"el":"4","count":1}]
複製代碼
這樣目前是沒有問題,可是這個函數承擔了三個職責。統計次數,處理長度,排序方式。並且,處理長度和排序方式,有其餘的原生處理方式,在這裏寫感受有些雞肋。函數
因此,重構這個API,就只保留統計次數這個職。至於長度和排序,有不少方式處理,slice,splice,length,sort等API或者屬性均可以處理。性能
/**
* @description 降序返回數組(字符串)每一個元素的出現次數
* @param arr
* @return {Array}
*/
getCount(arr) {
let obj = {}, k, arr1 = []
//記錄每一元素出現的次數
for (let i = 0, len = arr.length; i < len; i++) {
k = arr[i];
if (obj[k]) {
obj[k]++;
} else {
obj[k] = 1;
}
}
//保存結果{el-'元素',count-出現次數}
for (let o in obj) {
arr1.push({el: o, count: obj[o]});
}
//排序(降序)
arr1.sort(function (n1, n2) {
return n2.count - n1.count
});
return arr1;
},
複製代碼
checkType 檢測字符串類型。之前的實現方式是。學習
/**
* @description 檢測字符串
* @param str 待處理字符串
* @param type 待檢測的類型
*/
checkType(str, type) {
switch (type) {
case 'email':
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
case 'mobile':
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
case 'tel':
return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
case 'number':
return /^[0-9]$/.test(str);
case 'english':
return /^[a-zA-Z]+$/.test(str);
case 'text':
return /^\w+$/.test(str);
case 'chinese':
return /^[\u4E00-\u9FA5]+$/.test(str);
case 'lower':
return /^[a-z]+$/.test(str);
case 'upper':
return /^[A-Z]+$/.test(str);
default:
return true;
}
},
複製代碼
調用方式
ecDo.checkType('165226226326','mobile');
//result:false
複製代碼
由於 165226226326 不是一個有效的電話格式,因此返回false。可是這樣會存在一個問題就是,若是之後我想加什麼檢測的規則呢?好比增長一個密碼的規則。密碼能夠報錯大小寫字母,數字,點和下劃線。上面的方案,就是隻能在增長一個case。這樣改違反了開放-封閉原則,並且這樣會存在什麼問題,我在以前講策略模式的時候,已經說起,這裏不重複。
因此個人作法就是,給它增長擴展性。
/**
* @description 檢測字符串
*/
checkType:(function(){
let rules={
email(str){
return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str);
},
mobile(str){
return /^1[3|4|5|7|8][0-9]{9}$/.test(str);
},
tel(str){
return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str);
},
number(str){
return /^[0-9]$/.test(str);
},
english(str){
return /^[a-zA-Z]+$/.test(str);
},
text(str){
return /^\w+$/.test(str);
},
chinese(str){
return /^[\u4E00-\u9FA5]+$/.test(str);
},
lower(str){
return /^[a-z]+$/.test(str);
},
upper(str){
return /^[A-Z]+$/.test(str);
}
};
return {
/**
* @description 檢測接口
* @param str 待處理字符串
* @param type 待檢測的類型
*/
check(str, type){
return rules[type]?rules[type](str):false;
},
/**
* @description 添加規則擴展接口
* @param type 規則名稱
* @param fn 處理函數
*/
addRule(type,fn){
rules[type]=fn;
}
}
})(),
複製代碼
調用方式
console.log(ecDo.checkType.check('165226226326','mobile'));//false
ecDo.checkType.addRule('password',function (str) {
return /^[-a-zA-Z0-9._]+$/.test(str);
})
console.log(ecDo.checkType.check('***asdasd654zxc','password'));//false
複製代碼
調用麻煩了一些,可是擴展性有了,之後面對新的需求能夠更靈活的處理。
最少知識原則,官方一點的解釋是:一個對象應當對其餘對象有儘量少的瞭解。在下面表現爲:儘量的讓用戶更簡單,更方便的使用相關的API。具體表現看下面的例子
之前 trim 函數實現方式
/**
* @description 大小寫切換
* @param str 待處理字符串
* @param type 去除類型(1-全部空格 2-左右空格 3-左空格 4-右空格)
*/
trim(str, type) {
switch (type) {
case 1:
return str.replace(/\s+/g, "");
case 2:
return str.replace(/(^\s*)|(\s*$)/g, "");
case 3:
return str.replace(/(^\s*)/g, "");
case 4:
return str.replace(/(\s*$)/g, "");
default:
return str;
}
}
複製代碼
調用方式
//去除全部空格
ecDo.trim(' 1235asd',1);
//去除左空格
ecDo.trim(' 1235 asd ',3);
複製代碼
這樣的方式存在有目共睹,表明 type 參數的1,2,3,4能夠說是一個神仙數,雖然對於開發者而言,知道是什麼。可是若是有其餘人使用,那麼這樣的 API 就增長了記憶成本和調用的複雜性。
爲了解決這個問題,處理方式就分拆 API 。
/**
* @description 清除左右空格
*/
trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, "");
},
/**
* @description 清除全部空格
*/
trimAll(str){
return str.replace(/\s+/g, "");
},
/**
* @description 清除左空格
*/
trimLeft(str){
return str.replace(/(^\s*)/g, "");
},
/**
* @description 清除右空格
*/
trimRight(str){
return str.replace(/(\s*$)/g, "");
}
複製代碼
調用方式
//去除全部空格
ecDo.trim(' 123 5asd');
//去除左空格
ecDo.trimLeft(' 1235 asd ');
複製代碼
這樣 API 多了,可是記憶成本和調用簡單了。
下面的 API 在簡單使用方便,表現得更爲突出
原來方案
/**
* @description 加密字符串
* @param str 字符串
* @param regArr 字符格式
* @param type 替換方式
* @param ARepText 替換的字符(默認*)
*/
encryptStr(str, regArr, type = 0, ARepText = '*') {
let regtext = '',
Reg = null,
replaceText = ARepText;
//repeatStr是在上面定義過的(字符串循環複製),你們注意哦
if (regArr.length === 3 && type === 0) {
regtext = '(\\w{' + regArr[0] + '})\\w{' + regArr[1] + '}(\\w{' + regArr[2] + '})'
Reg = new RegExp(regtext);
let replaceCount = this.repeatStr(replaceText, regArr[1]);
return str.replace(Reg, '$1' + replaceCount + '$2')
}
else if (regArr.length === 3 && type === 1) {
regtext = '\\w{' + regArr[0] + '}(\\w{' + regArr[1] + '})\\w{' + regArr[2] + '}'
Reg = new RegExp(regtext);
let replaceCount1 = this.repeatStr(replaceText, regArr[0]);
let replaceCount2 = this.repeatStr(replaceText, regArr[2]);
return str.replace(Reg, replaceCount1 + '$1' + replaceCount2)
}
else if (regArr.length === 1 && type === 0) {
regtext = '(^\\w{' + regArr[0] + '})'
Reg = new RegExp(regtext);
let replaceCount = this.repeatStr(replaceText, regArr[0]);
return str.replace(Reg, replaceCount)
}
else if (regArr.length === 1 && type === 1) {
regtext = '(\\w{' + regArr[0] + '}$)'
Reg = new RegExp(regtext);
let replaceCount = this.repeatStr(replaceText, regArr[0]);
return str.replace(Reg, replaceCount)
}
},
複製代碼
調用方式
ecDo.encryptStr('18819322663',[3,5,3],0,'+')
//result:188+++++663
ecDo.encryptStr('18819233362',[3,5,3],1,'+')
//result:+++19233+++
ecDo.encryptStr('18819233362',[5],0)
//result:*****233362
ecDo.encryptStr('18819233362',[5],1)
//result:"188192*****"
複製代碼
這個 API 存在的問題也是同樣,太多的神仙數,好比[3,5,3],1,0等。相對於4-1的例子,這個對使用這形成的記憶成本和調用複雜性更大。甚至很容易會搞暈。若是是閱讀源碼,if-else的判斷,別說是其餘人了,就算是我這個開發者,我都會被搞蒙。
處理這些問題,也相似4-1。拆分 API 。
/**
* @description 加密字符串
* @param regIndex 加密位置 (開始加密的索引,結束加密的索引)
* @param ARepText 加密的字符 (默認*)
*/
encryptStr(str, regIndex, ARepText = '*') {
let regtext = '',
Reg = null,
_regIndex=regIndex.split(','),
replaceText = ARepText;
//repeatStr是在上面定義過的(字符串循環複製),你們注意哦
_regIndex=_regIndex.map(item=>+item);
regtext = '(\\w{' + _regIndex[0] + '})\\w{' + (1+_regIndex[1]-_regIndex[0]) + '}';
Reg = new RegExp(regtext);
let replaceCount = this.repeatStr(replaceText, (1+_regIndex[1]-_regIndex[0]));
return str.replace(Reg, '$1' + replaceCount);
},
/**
* @description 不加密字符串
* @param regIndex 不加密位置 (開始加密的索引,結束加密的索引)
* @param ARepText 不加密的字符 (默認*)
*/
encryptUnStr(str, regIndex, ARepText = '*') {
let regtext = '',
Reg = null,
_regIndex=regIndex.split(','),
replaceText = ARepText;
_regIndex=_regIndex.map(item=>+item);
regtext = '(\\w{' + _regIndex[0] + '})(\\w{' + (1+_regIndex[1]-_regIndex[0]) + '})(\\w{' + (str.length-_regIndex[1]-1) + '})';
Reg = new RegExp(regtext);
let replaceCount1 = this.repeatStr(replaceText, _regIndex[0]);
let replaceCount2 = this.repeatStr(replaceText, str.length-_regIndex[1]-1);
return str.replace(Reg, replaceCount1 + '$2' + replaceCount2);
},
/**
* @description 字符串開始位置加密
* @param regIndex 加密長度
* @param ARepText 加密的字符 (默認*)
*/
encryptStartStr(str,length,replaceText = '*'){
let regtext = '(\\w{' + length + '})';
let Reg = new RegExp(regtext);
let replaceCount = this.repeatStr(replaceText, length);
return str.replace(Reg, replaceCount);
},
/**
* @description 字符串結束位置加密
* @param regIndex 加密長度
* @param ARepText 加密的字符 (默認*)
*/
encryptEndStr(str,length,replaceText = '*'){
return this.encryptStartStr(str.split('').reverse().join(''),length,replaceText).split('').reverse().join('');
},
複製代碼
調用方式
console.log(`加密字符 ${ecDo.encryptStr('18819233362','3,7','+')}`)
//result:188+++++362
console.log(`不加密字符 ${ecDo.encryptUnStr('18819233362','3,7','+')}`)
//result:+++19233+++
console.log(`字符串開始位置加密 ${ecDo.encryptStartStr('18819233362','4')}`)
//result:****9233362
console.log(`字符串結束位置加密 ${ecDo.encryptEndStr('18819233362','4')}`)
//result:1881923****
複製代碼
結果同樣,可是調用就比以前簡單了,也不須要記憶太多東西。
相似4-1和4-2的改動還有幾個實例,在這裏就不列舉了!
這個實例與上面兩個實例不太同樣,上面兩個 API 爲了簡化使用,把一個 API 拆分紅多個,可是這個 API 是把多個 API 合併成一個。
/**
* @description 設置cookie
* @param name cookie名稱
* @param value 值
* @param iDay 有效時間(天數)
*/
setCookie(name, value, iDay) {
let oDate = new Date();
oDate.setDate(oDate.getDate() + iDay);
document.cookie = name + '=' + value + ';expires=' + oDate;
},
/**
* @description 獲取cookie
* @param name cookie名稱
*/
getCookie(name) {
let arr = document.cookie.split('; '),arr2;
for (let i = 0; i < arr.length; i++) {
arr2 = arr[i].split('=');
if (arr2[0] == name) {
return arr2[1];
}
}
return '';
},
/**
* @description 刪除cookie
* @param name cookie名稱
*/
removeCookie(name) {
this.setCookie(name, 1, -1);
},
複製代碼
調用方式
ecDo.setCookie(cookieName,'守候',1)//設置(有效時間爲1天)
ecDo.getCookie(cookieName)//獲取
ecDo.removeCookie(cookieName)//刪除
複製代碼
新增API
/**
* @description 操做cookie
* @param name cookie名稱
* @param value 值
* @param iDay 有效時間(天數)
*/
cookie(name, value, iDay){
if(arguments.length===1){
return this.getCookie(name);
}
else{
this.setCookie(name, value, iDay);
}
},
複製代碼
調用方式
ecDo.cookie(cookieName,'守候',1)//設置
ecDo.cookie(cookieName)//獲取
ecDo.cookie(cookieName,'守候',-1)//刪除(中間的值沒有意義了,只要cookie天數設置了-1,就會刪除。)
複製代碼
這樣調用,使用方法的記憶成本增長了,可是不須要記3個API,只須要記一個。
原來方案
/**
* @description 檢測密碼強度
*/
checkPwdLevel(str) {
let nowLv = 0;
if (str.length < 6) {
return nowLv
}
if (/[0-9]/.test(str)) {
nowLv++
}
if (/[a-z]/.test(str)) {
nowLv++
}
if (/[A-Z]/.test(str)) {
nowLv++
}
if (/[\.|-|_]/.test(str)) {
nowLv++
}
return nowLv;
},
複製代碼
調用方式
console.log(ecDo.checkPwdLevel('asd188AS19663362_.'));
//4
複製代碼
這樣寫沒問題,可是想必你們和我同樣,看到if有點多,並且if爲true的時候,作的事情仍是同樣的,就忍不住要折騰了。就有了下面的方案。
/**
* @description 檢測密碼強度
*/
checkPwdLevel(str) {
let nowLv = 0;
if (str.length < 6) {
return nowLv
}
//把規則整理成數組,再進行循環判斷
let rules=[/[0-9]/,/[a-z]/,/[A-Z]/,/[\.|-|_]/];
for(let i=0;i<rules.length;i++){
if(rules[i].test(str)){
nowLv++;
}
}
return nowLv;
},
複製代碼
這樣寫,處理的事情是同樣的,性能方面能夠忽略不計,可是看着舒服。
原來方案
/**
* @description 數組順序打亂
* @param arr
*/
upsetArr(arr) {
return arr.sort(() => {
return Math.random() - 0.5
});
},
複製代碼
調用方式
ecDo.upsetArr([1,2,3,4,5,6,7,8,9]);
複製代碼
這種方式沒錯,可是有個遺憾的地方就是不能實現徹底亂序,就是亂的不夠均勻。因此換了一種方式。
/**
* @description 數組順序打亂
* @param arr
* @return {Array.<T>}
*/
upsetArr(arr) {
let j,_item;
for (let i=0; i<arr.length; i++) {
j = Math.floor(Math.random() * i);
_item = arr[i];
arr[i] = arr[j];
arr[j] = _item;
}
return arr;
},
複製代碼
原理就是遍歷數組元素,而後將當前元素與之後隨機位置的元素進行交換,這樣亂序更加完全。
關於重構我本身的代碼庫,暫時就是這麼多了,這些實例只是部分,仍是一些 API 由於重構的目的,實現方式都基本同樣,就不重複舉例了。須要的到 github (ec-do-3.0.0-beta.1.js)上面看就好,關於我重構的這個文件,如今也只是一個 demo ,測試的階段,之後仍是繼續的改進。若是你們有什麼建議,或者須要增長什麼 API 歡迎在評論區瀏覽,你們多交流,多學習。
-------------------------華麗的分割線--------------------
想了解更多,關注關注個人微信公衆號:守候書閣