巧用匿名函數重構你的代碼
前言
國慶七天,大家的朋友圈攝影比賽如何啊?今日早讀文章由@Mapping分享。
正文從這開始~
匿名函數最早是LISP語言引入,後面發展為不僅是函數式語言所特有,在解釋型語言和編譯型語言中也越來越多地看到匿名函數的身影,它或許有個更潮的名字叫 lambda 表達式。
閉包多是用匿名函數實現,在匿名函數中引用了外部變數,那這個匿名函數就形成了閉包。由於閉包和匿名函數之間有著千絲萬縷的關係,所以經常會把兩者搞混淆。其實在 Js 中匿名函數、閉包、自執行函數、回調函數、箭頭函數,這些概念似乎相同,卻又不同,請讀者朋友自行了解。
匿名函數擁有可動態編程的執行過程。巧妙使用可以讓你的代碼簡約而不失優雅,靈活而不失約束。好了,正式切入本文的正題,巧用匿名函數重構代碼。按照重構的慣例,先指出代碼中的壞味(Bad Smell):
定義冗長的重複配置
條件多變的集合過濾
說一不二的方法調用
定義冗長的重複配置
在寫配置代碼時,經常會遇到大量重複的配置,如果要修改一些內容,所有的都要修改,著實很累,而且容易遺漏。比如:
{
html:`
Apple
Banana
Orange
`
}
這裡面有三個選項,三個選項的結構完全一樣,如果要給所有選項加一個 title,那你就要把這件事重複做三遍。
{
html:`
Apple
Banana
Orange
`
}
程序猿是個奇怪的群體,他們寧願做五件不同的事,也不願重複三次做同一件事。所以這怎麼能忍,把配置中不同的內容提取出來:
{
html:(function(fruits){
returnfruits.map(it=>`${it.title}`).join( );
}([
{name: apple ,title: Apple },
{name: banana ,title: Banana },
{name: orange ,title: Orange }
]))
}
這樣的修改對配置的調用方來說是完全透明的,但是對於配置的維護者來說,將結構和數據分離開,要改結構就改自執行方法體,要改數據就改傳入自執行方法的參數,可以大大減少犯錯的風險,又避免一些無腦的複製粘貼。
雖然在配置中寫代碼邏輯不是特別推薦的做法,但相對於代碼可維護性來說這不算啥了。
條件多變的集合過濾
集合過濾是個非常常見的需求,假設有個學生集合:
letstudents=[
{name: Lucy ,age:20,sex: female },
{name: LiLei ,age:21,sex: male },
{name: Jim ,age:18,sex: male }
]
現在要過濾出年齡 20 歲的同學:
functionfilterByAge(list,age){
letfiltered=[];
for(leti=;i
if(list[i].age===age){
filtered.push(list[i]);
}
}
returnfiltered;
}
又要過濾出姓名叫 LiLei 的同學:
functionfilterByName(list,name){
letfiltered=[];
for(leti=;i
if(list[i].name===name){
filtered.push(list[i]);
}
}
returnfiltered;
}
還要過濾出性別為男的同學:
functionfilterBySex(list,sex){
letfiltered=[];
for(leti=;i
if(list[i].sex===sex){
filtered.push(list[i]);
}
}
returnfiltered;
}
就在你覺得大功告成,可以站起彎伸腰的時刻,突然被一股強大的力量按回了座椅,幫我找出姓名以 「L」 開頭的童鞋。雖然你內心是 mmp~,但還能怎麼辦,寫啊,於是又吭哧吭哧加了如下方法:
functionfilterByNameStart(list,nameStart){
letfiltered=[];
for(leti=;i
if(list[i].name.indexOf(nameStart)===){
filtered.push(list[i]);
}
}
returnfiltered;
}
於是乎,這個調用方式:
filterByName(filterBySex(filterByAge(students,21), male ), LiLei );
就可以理解為找出年齡為 21 歲的男性 LiLei。好吧,可讀性還不算太差。
但是,不難看出以上 filterByAge、filterByNameStart 等這一系列方法中,除了過濾條件不同,其他邏輯完全一樣,造成了大量代碼的重複;並且沒有任何靈活性可言,調用方改需求,你就要加方法。
我們現在就使用匿名函數把不同的部分抽出去,讓調用方想怎麼過濾就過濾。filter 方法的主幹邏輯只關心當前的元素是不是要放入結果集合中,其他的判斷邏輯都交給匿名函數 fn 去做:
functionfilter(list,fn){
letfiltered=[];
for(leti=;i
if(fn(list[i],i)===true){
filtered.push(list[i]);
}
}
returnfiltered;
}
上面的例子會變成這種寫法:
filter(students,function(member){
returnmember.name=== LiLei &&member.age===21&&member.sex=== male ;
});
使用箭頭方法可以更簡潔:
filter(students,member=>member.name=== LiLei &&
member.age===21&&
member.sex=== male );
現在調用方再提一些變態的過濾方式,你可以回一個眼神,自己寫去。
說一不二的方法調用
假設有個 Api 介面,將傳入的數字翻倍並返回,這個介面支持單個和批量的方式。
傳入 5
執行成功返回
執行失敗返回
傳入 [2, 3]
執行成功返回 , ]}
執行失敗返回 , ]}
也就是單個輸入會按單個格式輸出,批量輸入會按批量格式輸出。三下五除二,實現了下面這個版本:
functionmultiple(inNum){
if(Array.isArray(inNum)){
// 處理批量情況
return{
status: success ,
data:inNum.map(it=>{
if(isNaN(parseFloat(it))){
return{
status: failed ,
error: The input is not a number
};
}
return{
status: success ,
data:it*2
}
})
};
}else{
// 處理單個情況
if(isNaN(parseFloat(inNum))){
return{
status: failed ,
error: The input is not a number
};
}
return{
status: success ,
data:inNum*2
};
}
}
這裡面單個和批量兩種方式除了輸入和輸出格式不同,其他邏輯完全一樣。如果要將乘 2 改成乘 3,兩個地方都要改。那看下怎麼使用匿名函數來避免兩處修改:
functionexecute(data,fn){
// 最小執行單元
letsingle=it=>{
try{
return{
status: success ,
data:fn(it)
};
}catch(e){
return{
status: failed ,
error:e.toString()
}
}
};
if(Array.isArray(data)){
return{
status: success ,
data:data.map(single)
}
}else{
returnsingle(data);
}
}
functionmultiple(inNum){
returnexecute(inNum,it=>{
if(isNaN(parseFloat(it))){
thrownewError( The input is not a number );
}
returnit*2;
});
}
現在 execute 方法只管輸入輸出的格式和錯誤處理,包攬了所有臟活累活;multiple 方法則只關心業務的具體實現,也不用關心輸入的是單個元素還是數組。如果要改乘 3,只要修改 multiple 方法最後一個 return。如此一來,execute 還可以被其他的 Api 方法復用,可謂一舉兩得。
總結
本文的目的只是拋磚引玉,代碼中可利用匿名函數重構的壞味還有很多,這種重構方式不只是適用於 Js 中。大家只要多思考、多動手,代碼質量一定會day day up~~
關於重構我還想多說一點,重構的過程應該是漸進的方式,當你改第一次的時候可能覺得還ok,第二次就要想下是不是有更好的方式來實現。如果修改對調用方透明那是最好了,實在不行讓調用方配合修改也是值得的,當然這其中還要權衡時間成本。
關於本文
作者:@Mapping
原文:https://iammapping.com/the-good-things-of-fn/
※跟隨 Google 工程師學習前端開發,是種怎樣的體驗?
※小馬過河——Angular 學起來難嗎?
※小馬過河——前後端是怎麼分家的?
※【視頻】Machines must suffer
※小馬過河——給萌新的 Angular 系列教程
TAG:前端早讀課 |