想成為一名合格的程序員,那么代碼優(yōu)化是相當(dāng)重要的一部分,一份高質(zhì)量的代碼,可以讓人一目了然,既方便自己,也方便他人。
if else
、switch case
是日常開發(fā)中最常見的條件判斷語句,這種看似簡單的語句,當(dāng)遇到復(fù)雜的業(yè)務(wù)場景時,如果處理不善,就會出現(xiàn)大量的邏輯嵌套,可讀性差并且難以擴展。
編寫高質(zhì)量可維護(hù)的代碼,我們先從最小處入手,一起來看看在前端開發(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
的多重嵌套層次減少到一層,更容易理解和維護(hù)。
function supply(fruit, quantity) {
const redFruits = ['apple', 'strawberry', 'cherry', 'cranberries'];
if(!fruit) throw new Error('沒有水果啦'); // 條件 1: 當(dāng) fruit 無效時,提前處理錯誤
if(!redFruits.includes(fruit)) return; // 條件 2: 當(dāng)不是紅色水果時,提前 return
console.log('紅色水果');
// 條件 3: 水果數(shù)量大于 10 個
if (quantity > 10) {
console.log('數(shù)量大于 10 個');
}
}
多條件分支的優(yōu)化處理
當(dāng)需要枚舉值處理不同的業(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
達(dá)到同樣的效果。
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);
}
(推薦教程:JavaScript教程)
使用數(shù)組新特性簡化邏輯判斷
巧妙的利用 ES6
中提供的數(shù)組新特性,也可以讓我們更輕松的處理邏輯判斷。
多條件判斷
編碼時遇到多個判斷條件時,本能的寫下下面的代碼(其實也是最能表達(dá)業(yè)務(wù)邏輯的面向過程編碼)。
function judge(fruit) {
if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'cherry' || fruit === 'cranberries' ) {
console.log('red');
}
}
但是當(dāng) type
未來到 10 種甚至更多時, 我們只能繼續(xù)添加 ||
來維護(hù)代碼么 ?
試試 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ù)默認(rèn)值
使用默認(rèn)參數(shù)
const buyFruit = (fruit,amount) => {
if(!fruit){
return
}
amount = amount || 1;
console.log(amount)
}
我們經(jīng)常需要處理函數(shù)內(nèi)部的一些參數(shù)默認(rèn)值,上面的代碼大家都不陌生,使用函數(shù)的默認(rèn)參數(shù),可以很好的幫助處理這種場景。
const buyFruit = (fruit,amount = 1) => {
if(!fruit){
return
}
console.log(amount,'amount')
}
我們可以通過 Babel
的轉(zhuǎn)譯來看一下默認(rèn)參數(shù)是如何實現(xiàn)的。
從上面的轉(zhuǎn)譯結(jié)果可以發(fā)現(xiàn),只有參數(shù)為 undefined
時才會使用默認(rèn)參數(shù)。
測試的執(zhí)行結(jié)果如下:
buyFruit('apple',''); // amount
buyFruit('apple',null); //null amount
buyFruit('apple'); //1 amount
所以使用默認(rèn)參數(shù)的情況下,我們需要注意的是默認(rèn)參數(shù) amount=1
并不等同于 amount || 1
。
使用解構(gòu)與默認(rèn)參數(shù)
當(dāng)函數(shù)參數(shù)是對象時,我們可以使用解構(gòu)結(jié)合默認(rèn)參數(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)
當(dāng)處理比較簡的對象時,解構(gòu)與默認(rèn)參數(shù)的配合是非常好的,但在一些復(fù)雜的場景中,我們面臨的可能是更復(fù)雜的結(jié)構(gòu)。
const oneComplexObj = {
firstLevel:{
secondLevel:[{
name:"",
price:""
}]
}
}
這個時候如果再通過解構(gòu)去獲取對象里的值。
const {
firstLevel:{
secondLevel:[{name,price]=[]
}={}
} = oneComplexObj;
可讀性就會比較差,而且需要考慮多層解構(gòu)的默認(rèn)值以及數(shù)據(jù)異常情況。
這種情況下,如果項目中使用 lodash
庫,可以使用其中的 lodash/get
方法。
import lodashGet from 'lodash/get';
const { name,price} = lodashGet(oneComplexObj,'firstLevel.secondLevel[0]',{});
策略模式優(yōu)化分支邏輯處理
策略模式:定義一系列的算法,把它們一個個封裝起來, 并且使它們可相互替換。
使用場景:策略模式屬于對象行為模式,當(dāng)遇到具有相同行為接口、行為內(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});
(推薦微課:JavaScript微課)
框架篇之 React JSX 邏輯判斷優(yōu)化
JSX
是一個看起來很像 XML
的 JavaScript
語法擴展。一般在 React
中使用 JSX
來描述界面信息,ReactDOM.render()
將 JSX
界面信息渲染到頁面上。
在 JSX
中支持 JavaScript
表達(dá)式,日常很常見的循環(huán)輸出子組件、三元表達(dá)式判斷、再復(fù)雜一些直接抽象出一個函數(shù)。
在 JSX
中寫這么多 JavaScript
表達(dá)式,整體代碼看起來會有點兒雜亂。試著優(yōu)化一下!
JSX-Control-Statements
JSX-Control-Statements
是一個 Babel
插件,它擴展了 JSX
的能力,支持以標(biāo)簽的形式處理條件判斷、循環(huán)。
If 標(biāo)簽
<If>
標(biāo)簽內(nèi)容只有在 condition
為 true
時才會渲染,等價于最簡單的三元表達(dá)式。
Before:
{ condition() ? 'Hello World!' : null }
After:
<If condition={ condition() }>Hello World!</If>
注意:<Else />
已被廢棄,復(fù)雜的條件判斷可以使用 <Choose>
標(biāo)簽。
Choose 標(biāo)簽
<Choose>
標(biāo)簽下包括至少一個 <When>
標(biāo)簽、可選的 <Otherwise>
標(biāo)簽。
<When>
標(biāo)簽內(nèi)容只有在 condition
為 true
時才會渲染,相當(dāng)于一個 if
條件判斷分支。
<Otherwise>
標(biāo)簽則相當(dāng)于最后的 else
分支。
Before:
{ test1 ? <span>IfBlock1</span> : test2 ? <span>IfBlock2</span> : <span>ElseBlock</span> }
After:
<Choose>
<When condition={ test1 }>
<span>IfBlock1</span>
</When>
<When condition={ test2 }>
<span>IfBlock2</span>
</When>
<Otherwise>
<span>ElseBlock</span>
</Otherwise>
</Choose>
For 標(biāo)簽
<For>
標(biāo)簽需要聲明 of
、each
屬性。
of
接收的是可以使用迭代器訪問的對象。
each
代表迭代器訪問時的當(dāng)前指向元素。
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>
注意:<For>
標(biāo)簽不能作為根元素。
With 標(biāo)簽
<With>
標(biāo)簽提供變量傳參的功能。
Before:
renderFoo = (foo) => {
return <span>{ foo }</span>;
}
// JSX 中表達(dá)式調(diào)用
{
this.renderFoo(47)
}
After:
<With foo={ 47 }>
<span>{ foo }</span>
</With>
使用這幾種標(biāo)簽優(yōu)化代碼,可以減少 JSX
中存在的顯式 JavaScript
表達(dá)式,使我們的代碼看上去更簡潔,但是這些標(biāo)簽封裝的能力,在編譯時需要轉(zhuǎn)換為等價的 JavaScript
表達(dá)式。
總結(jié)
以上就是一些常見的邏輯判斷優(yōu)化技巧總結(jié)。當(dāng)然,編寫高質(zhì)量可維護(hù)的代碼,除了邏輯判斷優(yōu)化,還需要有清晰的注釋、含義明確的變量命名、合理的代碼結(jié)構(gòu)拆分、邏輯分層解耦、以及更高層次的貼合業(yè)務(wù)的邏輯抽象等等,相信各位在這方面也有自己的一些心得。