編寫高質(zhì)量可維護的代碼之優(yōu)化邏輯判斷

編寫高質(zhì)量可維護的代碼,我們先從最小處入手,一起來看看在前端開發(fā)過程中,可以從哪些方面來優(yōu)化邏輯判斷?
下面我們會分別從 JavaScript 語法和 React JSX 語法兩個方面來分享一些優(yōu)化的技巧。
JavaScript 語法篇
嵌套層級優(yōu)化
function?supply(fruit,?quantity)?{
??const?redFruits?=?['apple',?'strawberry',?'cherry',?'cranberries'];
??//?條件?1:?水果存在
??if(fruit)?{
????//?條件?2:?屬于紅色水果
????if(redFruits.includes(fruit))?{
??????console.log('紅色水果');
??????//?條件?3:?水果數(shù)量大于?10?個
??????if?(quantity?>?10)?{
????????console.log('數(shù)量大于?10?個');
??????}
????}
??}?else?{
????throw?new?Error('沒有水果啦!');
??}
}
分析上面的條件判斷,存在三層 if 條件嵌套。
如果提前 return 掉無效條件,將 if else 的多重嵌套層次減少到一層,更容易理解和維護。
function?supply(fruit,?quantity)?{
??const?redFruits?=?['apple',?'strawberry',?'cherry',?'cranberries'];
??if(!fruit)?throw?new?Error('沒有水果啦');?????//?條件?1:?當?fruit?無效時,提前處理錯誤
??if(!redFruits.includes(fruit))?return;?//?條件?2:?當不是紅色水果時,提前?return
????
??console.log('紅色水果');
????
??//?條件?3:?水果數(shù)量大于?10?個
??if?(quantity?>?10)?{
????console.log('數(shù)量大于?10?個');
??}
}
多條件分支的優(yōu)化處理
當需要枚舉值處理不同的業(yè)務(wù)分支邏輯時,第一反應(yīng)是寫下 if else ?我們來看一下:
function?pick(color)?{
??//?根據(jù)顏色選擇水果
??if(color?===?'red')?{
????return?['apple',?'strawberry'];?
??}?else?if?(color?===?'yellow')?{
????return?['banana',?'pineapple'];
??}?else?if?(color?===?'purple')?{
????return?['grape',?'plum'];
??}?else?{
????return?[];
??}
}
在上面的實現(xiàn)中:
if else 分支太多 if else 更適合于條件區(qū)間判斷,而 switch case 更適合于具體枚舉值的分支判斷
使用 switch case 優(yōu)化上面的代碼后:
function?pick(color)?{
??//?根據(jù)顏色選擇水果
??switch?(color)?{
????case?'red':
??????return?['apple',?'strawberry'];
????case?'yellow':
??????return?['banana',?'pineapple'];
????case?'purple':
??????return?['grape',?'plum'];
????default:
??????return?[];
??}
}
switch case 優(yōu)化之后的代碼看上去格式整齊,思路很清晰,但還是很冗長。繼續(xù)優(yōu)化:
借助 Object 的 { key: value } 結(jié)構(gòu),我們可以在 Object 中枚舉所有的情況,然后將 key 作為索引,直接通過 Object.key 或者 Object[key] 來獲取內(nèi)容
const?fruitColor?=?{????????????????????????
??red:?['apple',?'strawberry'],
??yellow:?['banana',?'pineapple'],
??purple:?['grape',?'plum'],
}
function?pick(color)?{
??return?fruitColor[color]?||?[];
}
使用 Map 數(shù)據(jù)結(jié)構(gòu),真正的 (key, value) 鍵值對結(jié)構(gòu);
const?fruitColor?=?new?Map()
.set('red',?['apple',?'strawberry'])
.set('yellow',?['banana',?'pineapple'])
.set('purple',?['grape',?'plum']);
function?pick(color)?{
??return?fruitColor.get(color)?||?[];
}
優(yōu)化之后,代碼更簡潔、更容易擴展。
為了更好的可讀性,還可以通過更加語義化的方式定義對象,然后使用 Array.filter 達到同樣的效果。
const?fruits?=?[
??{?name:?'apple',?color:?'red'?},?
??{?name:?'strawberry',?color:?'red'?},?
??{?name:?'banana',?color:?'yellow'?},?
??{?name:?'pineapple',?color:?'yellow'?},?
??{?name:?'grape',?color:?'purple'?},?
??{?name:?'plum',?color:?'purple'?}
];
function?pick(color)?{
??return?fruits.filter(f?=>?f.color?==?color);
}
使用數(shù)組新特性簡化邏輯判斷
巧妙的利用 ES6 中提供的數(shù)組新特性,也可以讓我們更輕松的處理邏輯判斷。
多條件判斷
編碼時遇到多個判斷條件時,本能的寫下下面的代碼(其實也是最能表達業(yè)務(wù)邏輯的面向過程編碼)。
function?judge(fruit)?{
??if?(fruit?===?'apple'?||?fruit?===?'strawberry'?||?fruit?===?'cherry'?||?fruit?===?'cranberries'?)?{
????console.log('red');
??}
}
但是當 type 未來到 10 種甚至更多時, 我們只能繼續(xù)添加 || 來維護代碼么?
試試 Array.includes ~
//?將判斷條件抽取成一個數(shù)組
const?redFruits?=?['apple',?'strawberry',?'cherry',?'cranberries'];
function?judge(type)?{
??if?(redFruits.includes(fruit))?{
????console.log('red');
??}
}
判斷數(shù)組中是否所有項都滿足某條件
const?fruits?=?[
??{?name:?'apple',?color:?'red'?},
??{?name:?'banana',?color:?'yellow'?},
??{?name:?'grape',?color:?'purple'?}
];
function?match()?{
??let?isAllRed?=?true;
??//?判斷條件:所有的水果都必須是紅色
??for?(let?f?of?fruits)?{
????if?(!isAllRed)?break;
????isAllRed?=?(f.color?===?'red');
??}
??console.log(isAllRed);?//?false
}
上面的實現(xiàn)中,主要是為了處理數(shù)組中的所有項都符合條件。
使用 Array.every 可以很容的實現(xiàn)這個邏輯:
const?fruits?=?[
??{?name:?'apple',?color:?'red'?},
??{?name:?'banana',?color:?'yellow'?},
??{?name:?'grape',?color:?'purple'?}
];
function?match()?{
??//?條件:所有水果都必須是紅色
??const?isAllRed?=?fruits.every(f?=>?f.color?==?'red');
??console.log(isAllRed);?//?false
}
判斷數(shù)組中是否有某一項滿足條件
Array.some,它主要處理的場景是判斷數(shù)組中是否有一項滿足條件。
如果想知道是否有紅色水果,可以直接使用 Array.some 方法:
const?fruits?=?[
????{?name:?'apple',?color:?'red'?},
????{?name:?'banana',?color:?'yellow'?},
????{?name:?'grape',?color:?'purple'?}
??];
//?條件:是否有紅色水果?
const?isAnyRed?=?fruits.some(f?=>?f.color?==?'red');
還有許多其他數(shù)組新特性,比如 Array.find、Array.slice、Array.findIndex、Array.reduce、Array.splice 等,在實際場景中可以根據(jù)需要選擇使用。
函數(shù)默認值
使用默認參數(shù)
const?buyFruit?=?(fruit,amount)?=>?{
??if(!fruit){
?? return
??}
??amount?=?amount?||?1;
??console.log(amount)
}
我們經(jīng)常需要處理函數(shù)內(nèi)部的一些參數(shù)默認值,上面的代碼大家都不陌生,使用函數(shù)的默認參數(shù),可以很好的幫助處理這種場景。
const?buyFruit?=?(fruit,amount?=?1)?=>?{
??if(!fruit){
?? return
??}
??console.log(amount,'amount')
}
我們可以通過 Babel 的轉(zhuǎn)譯來看一下默認參數(shù)是如何實現(xiàn)的。

從上面的轉(zhuǎn)譯結(jié)果可以發(fā)現(xiàn),只有參數(shù)為 undefined 時才會使用默認參數(shù)。
測試的執(zhí)行結(jié)果如下:
buyFruit('apple','');??//?amount
buyFruit('apple',null);??//null?amount
buyFruit('apple');??//1?amount
所以使用默認參數(shù)的情況下,我們需要注意的是默認參數(shù) amount = 1 并不等同于 amount || 1。
使用解構(gòu)與默認參數(shù)
當函數(shù)參數(shù)是對象時,我們可以使用解構(gòu)結(jié)合默認參數(shù)來簡化邏輯。
Before:
const?buyFruit?=?(fruit,amount)?=>?{
?fruit?=?fruit?||?{};
?if(!fruit.name?||?!fruit.price){
?? return;
?}
?...
??amount?=?amount?||?1;
??console.log(amount)
}
After:
const?buyFruit?=?({?name,price?}={},amount)?=>?{
??if(!name?||?!prices){
??? return;
??}
??console.log(amount)
}
復(fù)雜數(shù)據(jù)解構(gòu)
當處理比較簡的對象時,解構(gòu)與默認參數(shù)的配合是非常好的,但在一些復(fù)雜的場景中,我們面臨的可能是更復(fù)雜的結(jié)構(gòu)。
const?oneComplexObj?=?{
?firstLevel: {
?? secondLevel:[{
??? name:"",
??? price:""
?? }]
? }
}
這個時候如果再通過解構(gòu)去獲取對象里的值。
const?{
??firstLevel:{
????secondLevel:[{name,price]=[]
??}={}
}?=?oneComplexObj;??????????????
可讀性就會比較差,而且需要考慮多層解構(gòu)的默認值以及數(shù)據(jù)異常情況。
這種情況下,如果項目中使用 lodash 庫,可以使用其中的 lodash/get 方法。
import?lodashGet?from?'lodash/get';
const?{?name,price}?=?lodashGet(oneComplexObj,'firstLevel.secondLevel[0]',{});
策略模式優(yōu)化分支邏輯處理
策略模式:定義一系列的算法,把它們一個個封裝起來, 并且使它們可相互替換。
使用場景:策略模式屬于對象行為模式,當遇到具有相同行為接口、行為內(nèi)部不同邏輯實現(xiàn)的實例對象時,可以采用策略模式;或者是一組對象可以根據(jù)需要動態(tài)的選擇幾種行為中的某一種時,也可以采用策略模式;這里以第二種情況作為示例:
Before:
const?TYPE?=?{
??JUICE:'juice',
??SALAD:'salad',
??JAM:'jam'
}
function?enjoy({type?=?TYPE.JUICE,fruits}){
??if(!fruits?||?!fruits.length)?{
????console.log('請先采購水果!');
????return;
?}
??if(type?===?TYPE.JUICE)?{
????console.log('榨果汁中...');
????return?'果汁';
??}
??if(type?===?TYPE.SALAD)?{
????console.log('做沙拉中...');
????return?'拉沙';
??}
??if(type?===?TYPE.JAM)?{
????console.log('做果醬中...');
????return?'果醬';
??}
??return;
}
enjoy({type:'juice',fruits});
使用思路:定義策略對象封裝不同行為、提供策略選擇接口,在不同的規(guī)則時調(diào)用相應(yīng)的行為。
After:
const?TYPE?=?{
??JUICE:'juice',
??SALAD:'salad',
??JAM:'jam'
}
const?strategies?=?{
??[TYPE.JUICE]:?function(fruits){
????console.log('榨果汁中...');
????return?'果汁';
??},
??[TYPE.SALAD]:function(fruits){
????console.log('做沙拉中...');
????return?'沙拉';
??},
??[TYPE.JAM]:function(fruits){
????console.log('做果醬中...');
????return?'果醬';
??},
}
function?enjoy({type?=?TYPE.JUICE,fruits})?{
??if(!type)?{
????console.log('請直接享用!');
????return;
?}
??if(!fruits?||?!fruits.length)?{
????console.log('請先采購水果!');
????return;
?}
???return?strategies[type](fruits);
}
enjoy({type:'juice',fruits});
框架篇之 React JSX 邏輯判斷優(yōu)化
JSX 是一個看起來很像 XML 的 JavaScript 語法擴展。一般在 React 中使用 JSX 來描述界面信息,ReactDOM.render() 將 JSX 界面信息渲染到頁面上。
在 JSX 中支持 JavaScript 表達式,日常很常見的循環(huán)輸出子組件、三元表達式判斷、再復(fù)雜一些直接抽象出一個函數(shù)。
在 JSX 中寫這么多 ?JavaScript 表達式,整體代碼看起來會有點兒雜亂。試著優(yōu)化一下!
JSX-Control-Statements
JSX-Control-Statements (https://www.npmjs.com/package/jsx-control-statements) 是一個 Babel 插件,它擴展了 JSX 的能力,支持以標簽的形式處理條件判斷、循環(huán)。
If 標簽
Before:
{?condition()???'Hello?World!'?:?null?}???
After:
<If?condition={?condition()?}>Hello?World!If>???
注意:
Choose 標簽
Before:
{?test1???<span>IfBlock1span>?:?test2???<span>IfBlock2span>?:?<span>ElseBlockspan>?}
After:
<Choose>
??<When?condition={?test1?}>
????<span>IfBlock1span>
??When>
??<When?condition={?test2?}>
????<span>IfBlock2span>
??When>
??<Otherwise>
????<span>ElseBlockspan>
??Otherwise>
Choose>
For 標簽
of 接收的是可以使用迭代器訪問的對象。
each 代表迭代器訪問時的當前指向元素。
Before:
{
??(this.props.items?||?[]).map(item?=>?{
????return?<span?key={?item.id?}>{?item.title?}span>
??})
}
After:
<For?each="item"?of={?this.props.items?}>
??<span?key={?item.id?}>{?item.title?}span>
For>
注意:
With 標簽
Before:
renderFoo?=?(foo)?=>?{
??return?<span>{?foo?}span>;
}
//?JSX?中表達式調(diào)用
{
??this.renderFoo(47)
}
After:
<With?foo={?47?}>
??<span>{?foo?}span>
With>
使用這幾種標簽優(yōu)化代碼,可以減少 ?JSX 中存在的顯式 JavaScript 表達式,使我們的代碼看上去更簡潔,但是這些標簽封裝的能力,在編譯時需要轉(zhuǎn)換為等價的 JavaScript 表達式。
注意:具體 babel-plugin-jsx-control-statements 插件的使用見第三篇參考文章;Vue 框架已經(jīng)通過指令的形式支持 v-if、v-else-if、v-else、v-show、slot 等。
總結(jié)
以上我們總結(jié)了一些常見的邏輯判斷優(yōu)化技巧。當然,編寫高質(zhì)量可維護的代碼,除了邏輯判斷優(yōu)化,還需要有清晰的注釋、含義明確的變量命名、合理的代碼結(jié)構(gòu)拆分、邏輯分層解耦、以及更高層次的貼合業(yè)務(wù)的邏輯抽象等等,相信各位在這方面也有自己的一些心得,歡迎一起留言討論~
參考文獻
5 Tips to Write Better Conditionals in JavaScript (https://scotch.io/bar-talk/5-tips-to-write-better-conditionals-in-javascript) stop-putting-so-many-if-statements-in-your-javascript (https://medium.com/better-programming/stop-putting-so-many-if-statements-in-your-javascript-3b65aaa4b86b) JSX Control Statements (https://www.npmjs.com/package/jsx-control-statements)
看完兩件事

