ctrl+c 和 ctrl+v 給我們帶來了很多的便利,但是也使我們變得懶惰,不願思考。
1.前言
相信很多人和我一樣,在開發專案的時候,因為專案趕,或者一時沒想到等原因。頻繁使用 ctrl+c 和 ctrl+v ,導致程式碼很多都是重複的。這幾天,也看了自己以前寫的程式碼,簡單的探索了一下,挑選幾個例項,分享下如何在特定場景下,保證程式碼質量前提下,提高程式碼複用性。
提高程式碼的複用性,應該是不同場景,不同解決方案的。同時也要保證程式碼質量。不建議強制提高程式碼複用性,如果提高程式碼複用性會大大的降低程式碼的可讀性,維護性,可能會得不償失。
2.HTML+CSS
在做專案的時候,相信頁面上總會有很多相似的按鈕,比如下圖,專案上幾個相似的按鈕。面對這樣的需求,之前是直接寫三個按鈕
<button type="button" class='u-btn-yes-samll'>確定</button>
<button type="button" class='u-btn-yes'>確定</button>
<button type="button" class='u-btn-yes-big'>確定</button>
複製程式碼
css
button{
border:none;
font-family:'微軟雅黑';
}
.u-btn-yes{
width:80px;
height:36px;
font-size:14px;
color:#fff;
border-radius:10px;
background:#09f;
}
.u-btn-yes-samll{
width:60px;
height:30px;
font-size:12px;
color:#fff;
border-radius:10px;
background:#09f;
}
.u-btn-yes-big{
width:100px;
height:40px;
font-size:16px;
color:#fff;
border-radius:10px;
background:#09f;
}
複製程式碼
這樣相當於每增加一種按鈕,就增加了幾行的 css 程式碼,實際上改變的只有 width ,height ,font-size 這三個屬性。
實際上可以根據大小進行程式碼複用。
.u-btn-yes{
width:80px;
height:36px;
font-size:14px;
color:#fff;
border-radius:10px;
background:#09f;
}
.u-btn-yes.u-btn-samll{
width:60px;
height:30px;
font-size:12px;
}
.u-btn-yes.u-btn-big{
width:100px;
height:40px;
font-size:16px;
}
複製程式碼
頁面呼叫
<button type="button" class='u-btn-yes u-btn-samll'>確定</button>
<button type="button" class='u-btn-yes'>確定</button>
<button type="button" class='u-btn-yes u-btn-big'>確定</button>
複製程式碼
頁面上可能還有很多按鈕類似的,但是不同顏色的(如下圖),也可以靈活處理,這樣還可以自由組合。這個也是社群上很多 UI 庫所使用的方式。
.u-btn{
width:80px;
height:36px;
font-size:14px;
color:#fff;
border-radius:10px;
background:#09f;
}
.u-btn-samll{
width:60px;
height:30px;
font-size:12px;
}
.u-btn-big{
width:100px;
height:40px;
font-size:16px;
}
.u-btn-red{
background:#f33;
}
.u-btn-yellow{
background:#f90;
}
複製程式碼
html
<button type="button" class='u-btn u-btn-samll'>確定</button>
<button type="button" class='u-btn u-btn-red'>確定</button>
<button type="button" class='u-btn u-btn-big u-btn-yellow'>確定</button>
複製程式碼
對於這些按鈕,不建議設定 margin ,positon 等樣式,因為不同的按鈕在不同的地方,上面這幾個屬性基本不會一樣。
3.JavaScript
關於提高程式碼複用性的好處,在上面 HTML+CSS的例項裡面並沒有很明顯的優勢,但在 JS 裡面提高程式碼的複用性優勢就比較明顯了,下面簡單列舉幾個例子。
3-1.封裝常用函式
在上家公司的專案裡面有這樣程式碼,目的也很明顯,就是使用者填寫表單的時候,還沒有填寫完整就提交,前端這裡就需要給一個簡單的提示。
//當資訊沒填寫完整的時候彈出提示
layer.alert('請檢查資訊是否填寫完整',{
title:'提示',
icon:2
})
複製程式碼
基於 layer 這個開源庫,程式碼看得特別的簡單。但是隨著專案的開發,使用者填寫表單的地方有多個,那麼上面的程式碼就會被複制到多個地方,這樣難免會有有點多餘。
另外,這樣做最大的一個問題就是:如果上面的程式碼在專案上有20個地方在用,有一天需求變了,title 這個屬性值要從‘提示’變成‘警告’。那就麻煩了,要找20個地方,即使編輯器有全域性替換的功能,這樣的改動出問題的概率也比較大。面對這樣的情況。之前處理的方法是對這個彈窗進行簡單粗暴的封裝,方便複用。
function openTips(){
//當資訊沒填寫完整的時候彈出提示
layer.alert('請檢查資訊是否填寫完整',{
title:'提示',
icon:2
});
}
複製程式碼
在需要的地方,需要的時候進行呼叫就好,這樣可以寫少很多程式碼!修改起來,也只需要修改 openTips 這一個地方就完事了。
openTips();
複製程式碼
3-2.使用策略模式代替 switch
下面再看一個例項。借用下之前群友的發的程式碼。
模擬資料
let listWarnConf = [
{
warnType: 1,
warnCondition: 220,
},
{
warnType: 2,
warnCondition: 36,
},
{
warnType: 3,
warnCondition: 45,
},
{
warnType: 4,
warnCondition: 110,
},
{
warnType: 5,
warnCondition: 380,
}
]
複製程式碼
業務邏輯程式碼
listWarnConf.forEach(item => {
switch(item.warnType) {
case 1:
item.warnTypeText = '超壓';
item.warnConditionText = `電壓高於${item.warnCondition}V`
break;
case 2:
item.warnTypeText = '欠壓';
item.warnConditionText = `電壓低於${item.warnCondition}V`
break
case 3:
item.warnTypeText = '超載';
item.warnConditionText = `電流高於${item.warnCondition}A`
break
case 4:
item.warnTypeText = '電壓不平衡';
item.warnConditionText = `電壓不平衡高於${item.warnCondition}%`
break
case 5:
item.warnTypeText = '電流不平衡';
item.warnConditionText = `電流不平衡${item.warnCondition}%`
break
}
})
複製程式碼
這樣看著結果是沒問題的,但是看著那麼多 case 執行的都是賦值操作。而且最大的問題和上面一樣,如果多個地方使用,需求變了,那麼還是要修改這麼多的地方,下面優化下,讓程式碼的複用性提高下。
//設定配置資料
let warnConfig={
1:{
warnTypeText:'超壓',
warnConditionText:'電壓高於replaceTextV'
},
2:{
warnTypeText:'欠壓',
warnConditionText:'電壓低於replaceTextV'
},
3:{
warnTypeText:'超載',
warnConditionText:'電流高於replaceTextV'
},
4:{
warnTypeText:'電壓不平衡',
warnConditionText:'電壓不平衡高於replaceText%'
},
5:{
warnTypeText:'電流不平衡',
warnConditionText:'電流不平衡高於replaceText%'
}
}
//業務邏輯--根據配置資料設定warnTypeText和warnConditionText
listWarnConf.forEach(item => {
item.warnTypeText=warnConfig[item.warnType].warnTypeText;
item.warnConditionText=warnConfig[item.warnType].warnConditionText.replace('replaceText',item.warnCondition);
})
複製程式碼
這樣改程式碼量沒減少,可讀性比 switch 差,但能讀懂。但是這樣做就是重複的程式碼少了,配置資料和業務邏輯分離了,如果以後要修改配置資料或者業務邏輯,就修改其中一項即可,互相不影響。把配置資料抽出來公用,那麼在需要修改的時候,直接修改就好。
關於提高程式碼的複用性,或者說減少重複的程式碼,個人覺可以往以下目標努力--當需求發生改變,需要修改程式碼的時候,同樣的程式碼不要修改兩次。
3-3.保持函式單一職責,靈活組合
保持函式的單一職責,保證一個函式只執行一個動作,每個動作互不影響,可以自由組合,就可以提高程式碼的複用性。
比如下面的程式碼,從服務端請求回來的訂單資料如下,需要進行以下處理 1.根據 status 進行對應值得顯示(0-進行中,1-已完成,2-訂單異常) 2.把 startTime 由時間戳顯示成 yyyy-mm-dd 3.如果欄位值為空字串 ,設定欄位值為 ‘--’
let orderList=[
{
id:1,
status:0,
startTime:1538323200000,
},
{
id:2,
status:2,
startTime:1538523200000,
},
{
id:3,
status:1,
startTime:1538723200000,
},
{
id:4,
status:'',
startTime:'',
},
];
複製程式碼
需求似乎很簡單,程式碼也少
let _status={
0:'進行中',
1:'已完成',
2:'訂單異常'
}
orderList.forEach(item=>{
//設定狀態
item.status=item.status.toString()?_status[item.status]:'';
//設定時間
item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
//設定--
for(let key in item){
if(item[key]===''){
item[key]='--';
}
}
})
複製程式碼
執行結果也正常,但是這樣寫程式碼重複性會很多,比如下面,另一組重服務端請求回來的使用者資料,使用者資料沒有 status,startTime,兩個欄位,而且需要根據 type 對應顯示使用者的身份(0-普通使用者,1-vip,2-超級vip)。
let userList=[
{
id:1,
name:'守候',
type:0
},
{
id:2,
name:'浪跡天涯',
type:1
},
{
id:3,
name:'曾經',
type:2
}
]
複製程式碼
出現這樣的需求,之前寫的程式碼無法重用,只能複製過來,再修改下。
let _type={
0:'普通使用者',
1:'vip',
2:'超級vip'
}
userList.forEach(item=>{
//設定type
item.type=item.type.toString()?_type[item.type]:'';
//設定--
for(let key in item){
if(item[key]===''){
item[key]='--';
}
}
})
複製程式碼
結果正常,想必大家已經發現問題了,程式碼有點多餘。下面就使用單一職責的原則改造下操作函式,設定 status,startTime,type,-- 。這裡拆分成四個函式。
let handleFn={
setStatus(list){
let _status={
0:'進行中',
1:'已完成',
2:'訂單異常'
}
list.forEach(item=>{
item.status=item.status.toString()?_status[item.status]:'';
})
return list
},
setStartTime(list){
list.forEach(item=>{
item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
})
return list;
},
setInfo(list){
list.forEach(item=>{
for(let key in item){
if(item[key]===''){
item[key]='--';
}
}
})
return list;
},
setType(list){
let _type={
0:'普通使用者',
1:'vip',
2:'超級vip'
}
list.forEach(item=>{
item.type=item.type.toString()?_type[item.type]:'';
})
return list;
}
}
複製程式碼
下面直接呼叫函式就好
//處理訂單資料
orderList=handleFn.setStatus(orderList);
orderList=handleFn.setStartTime(orderList);
orderList=handleFn.setInfo(orderList);
console.log(orderList);
//處理使用者資料
userList=handleFn.setType(userList);
userList=handleFn.setInfo(userList);
console.log(userList);
複製程式碼
執行結果也正常
如果嫌棄連續賦值麻煩,可以借用 jQuery 的那個思想,進行鏈式呼叫。
let ec=(function () {
let handle=function (obj) {
//深拷貝物件
this.obj=JSON.parse(JSON.stringify(obj));
};
handle.prototype={
/**
* @description 設定保密資訊
*/
setInfo(){
this.obj.map(item=>{
for(let key in item){
if(item[key]===''){
item[key]='--';
}
}
});
return this;
},
/**
* @description 設定狀態
*/
setStatus(){
let _status={
0:'進行中',
1:'已完成',
2:'訂單異常'
}
this.obj.forEach(item=>{
item.status=item.status.toString()?_status[item.status]:''
});
return this;
},
/**
* @description 設定時間
*/
setStartTime(){
this.obj.forEach(item=>{
item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
});
return this;
},
/**
* @description 設定type
*/
setType(){
let _type={
0:'普通使用者',
1:'vip',
2:'超級vip'
}
this.obj.forEach(item=>{
item.type=item.type.toString()?_type[item.type]:'';
})
return this;
},
/**
* @description 返回處理結果
* @return {Array|*}
*/
end(){
return this.obj;
}
}
//暴露建構函式介面
return function (obj) {
return new handle(obj);
}
})();
複製程式碼
這樣就可以鏈式呼叫了
//處理訂單資料
orderList=ec(orderList).setStatus().setStartTime().setInfo().end();
console.log(orderList);
//處理使用者資料
userList=ec(userList).setType().end();
console.log(userList);
複製程式碼
事情到這裡了,相信大家發現一個很嚴重的問題就是迴圈的次數增加了。沒優化之前,只需要迴圈一次,就可以把設定狀態,設定時間,設定--這些步驟都完成,但是現在 setStatus().setStartTime().setInfo()
這裡的程式碼,每執行一個函式,都遍歷了一次陣列,這個就得優化下。處理的方式就是在每一個函式裡面,只記錄要處理什麼,但是不進行處理,等到執行到 end 的時候再統一處理,以及返回。
let ec=(function () {
let handle=function (obj) {
//深拷貝物件
this.obj=JSON.parse(JSON.stringify(obj));
//記錄要處理的步驟
this.handleFnList=[];
};
handle.prototype={
/**
* @description 設定保密資訊
*/
handleSetInfo(item){
for(let key in item){
if(item[key]===''){
item[key]='--';
}
}
return this;
},
setInfo(){
this.handleFnList.push('handleSetInfo');
return this;
},
/**
* @description 設定狀態
*/
handleSetStatus(item){
let _status={
0:'進行中',
1:'已完成',
2:'訂單異常'
}
item.status=item.status.toString()?_status[item.status]:''
return item;
},
setStatus(){
this.handleFnList.push('handleSetStatus');
return this;
},
/**
* @description 設定時間
*/
handleSetStartTime(item){
item.startTime=item.startTime.toString()?new Date(item.startTime).toLocaleDateString().replace(/\//g,'-'):'';
return item;
},
setStartTime(){
this.handleFnList.push('handleSetStartTime');
return this;
},
/**
* @description 設定type
*/
handleSetType(item){
let _type={
0:'普通使用者',
1:'vip',
2:'超級vip'
}
item.type=item.type.toString()?_type[item.type]:'';
return item;
},
setType(){
this.handleFnList.push('handleSetType');
return this;
},
/**
* @description 返回處理結果
* @return {Array|*}
*/
end(){
//統一處理操作
this.obj.forEach(item=>{
this.handleFnList.forEach(fn=>{
item=this[fn](item);
})
})
return this.obj;
}
}
//暴露建構函式介面
return function (obj) {
return new handle(obj);
}
})();
複製程式碼
這樣改,之前的呼叫方式不需要改變,然後結果也是正確的
可能大家會覺得很簡單一個需求,卻搞得這麼複雜。如果這樣想是正確的,因為這個的確搞複雜了,可讀性也差了,但想到專案遇到的處理資料不止這一些,還有比如金額的格式顯示,其它資料的各種狀態碼解析顯示,銀行卡號每隔4位分割,電話號碼的顯示等等。所以就先封裝一下,以後用的時候,直接使用。不知道算不算是先苦後甜?如果需求比較簡單,可能真的沒必要這麼樣封裝。
4.小結
假期看程式碼,提高程式碼複用性的總結,差不多就是這些了,當然還有一些例項,但是在之前已經寫過了,和該文章提及的例項也是大同小異,就不再重複提及。提高程式碼的複用性是一個很大的話題,如果大家有什麼好的建議,例項,歡迎分享。
-------------------------華麗的分割線--------------------
想了解更多,和我交流,內推職位,請新增我微信。或者關注我的微信公眾號:守候書閣