使用 JavaScript 編寫更好的條件語句
?來源:cuppar在任何編程語言中,代碼需要根據(jù)不同的條件在給定的輸入中做不同的決定和執(zhí)行相應(yīng)的動作。
例如,在一個游戲中,如果玩家生命點為0,游戲結(jié)束。在天氣應(yīng)用中,如果在早上被查看,顯示一個日出圖片,如果是晚上,則顯示星星和月亮。在這篇文章中,我們將探索JavaScript中所謂的條件語句如何工作。
如果你使用JavaScript工作,你將寫很多包含條件調(diào)用的代碼。條件調(diào)用可能初學(xué)很簡單,但是還有比寫一對對if/else更多的東西。這里有些編寫更好更清晰的條件代碼的有用提示。
數(shù)組方法 Array.includes 提前退出 / 提前返回 用對象字面量或Map替代Switch語句 默認(rèn)參數(shù)和解構(gòu) 用 Array.every & Array.some 匹配全部/部分內(nèi)容 使用可選鏈和空值合并
1. 數(shù)組方法 Array.includes
使用 Array.includes 進(jìn)行多條件選擇
例如:
function?printAnimals(animal)?{
????if?(animal?===?'dog'?||?animal?===?'cat')?{
????????console.log(I?have?a?${animal});
????}
}
console.log(printAnimals('dog'));?//?I?have?a?dog
上面的代碼看起來很好因為我們只檢查了兩個動物。然而,我們不確定用戶輸入。如果我們要檢查任何其他動物呢?如果我們通過添加更多“或”語句來擴(kuò)展,代碼將變得難以維護(hù)和不清晰。
解決方案:
我們可以通過使用 Array.includes 來重寫上面的條件
function?printAnimals(animal)?{
???const?animals?=?['dog',?'cat',?'hamster',?'turtle'];?
???if?(animals.includes(animal))?{
?????console.log(I?have?a?${animal});
???}
}
console.log(printAnimals('hamster'));?//?I?have?a?hamster
這里,我們創(chuàng)建來一個動物數(shù)組,所以條件語句可以和代碼的其余部分抽象分離出來。現(xiàn)在,如果我們想要檢查任何其他動物,我們只需要添加一個新的數(shù)組項。
我們也能在這個函數(shù)作用域外部使用這個動物數(shù)組變量來在代碼中的其他任意地方重用它。這是一個編寫更清晰、易理解和維護(hù)的代碼的方法,不是嗎?
2. 提前退出 / 提前返回
這是一個精簡你的代碼的非??岬募记伞N矣浀卯?dāng)我開始專業(yè)工作時,我在第一天學(xué)習(xí)使用提前退出來編寫條件。
讓我們在之前的例子上添加更多的條件。用包含確定屬性的對象替代簡單字符串的動物。
現(xiàn)在的需求是:
如果沒有動物,拋出一個異常 打印動物類型 打印動物名字 打印動物性別
const?printAnimalDetails?=?animal?=>?{
??let?result;?//?declare?a?variable?to?store?the?final?value
??//?condition?1:?check?if?animal?has?a?value
??if?(animal)?{
????//?condition?2:?check?if?animal?has?a?type?property
????if?(animal.type)?{
??????//?condition?3:?check?if?animal?has?a?name?property
??????if?(animal.name)?{
????????//?condition?4:?check?if?animal?has?a?gender?property
????????if?(animal.gender)?{
??????????result?=?${animal.name}?is?a?${animal.gender}?${animal.type};;
????????}?else?{
??????????result?=?"No?animal?gender";
????????}
??????}?else?{
????????result?=?"No?animal?name";
??????}
????}?else?{
??????result?=?"No?animal?type";
????}
??}?else?{
????result?=?"No?animal";
??}
??return?result;
};
console.log(printAnimalDetails());?//?'No?animal'
console.log(printAnimalDetails({?type:?"dog",?gender:?"female"?}));?//?'No?animal?name'
console.log(printAnimalDetails({?type:?"dog",?name:?"Lucy"?}));?//?'No?animal?gender'
console.log(
??printAnimalDetails({?type:?"dog",?name:?"Lucy",?gender:?"female"?})
);?//?'Lucy?is?a?female?dog'
你覺得上面的代碼怎么樣?
它工作得很好,但是代碼很長并且維護(hù)困難。如果不使用lint工具,找出閉合花括號在哪都會浪費很多時間。? 想象如果代碼有更復(fù)雜的邏輯會怎么樣?大量的if..else語句。
我們能用三元運算符、&&條件等語法重構(gòu)上面的功能,但讓我們用多個返回語句編寫更清晰的代碼。
const?printAnimalDetails?=?({type,?name,?gender?}?=?{})?=>?{
??if(!type)?return?'No?animal?type';
??if(!name)?return?'No?animal?name';
??if(!gender)?return?'No?animal?gender';
//?Now?in?this?line?of?code,?we're?sure?that?we?have?an?animal?with?all?//the?three?properties?here.
??return?${name}?is?a?${gender}?${type};
}
console.log(printAnimalDetails());?//?'No?animal?type'
console.log(printAnimalDetails({?type:?dog?}));?//?'No?animal?name'
console.log(printAnimalDetails({?type:?dog,?gender:?female?}));?//?'No?animal?name'
console.log(printAnimalDetails({?type:?dog,?name:?'Lucy',?gender:?'female'?}));?//?'Lucy?is?a?female?dog'
在這個重構(gòu)過的版本中,也包含了解構(gòu)和默認(rèn)參數(shù)。默認(rèn)參數(shù)確保如果我們傳遞undefined作為一個方法的參數(shù),我們?nèi)匀挥兄悼梢越鈽?gòu),在這里它是一個空對象{}。
通常,在專業(yè)領(lǐng)域,代碼被寫在這兩種方法之間。
另一個例子:
function?printVegetablesWithQuantity(vegetable,?quantity)?{
??const?vegetables?=?['potato',?'cabbage',?'cauliflower',?'asparagus'];
??//?condition?1:?vegetable?should?be?present
???if?(vegetable)?{
?????//?condition?2:?must?be?one?of?the?item?from?the?list
?????if?(vegetables.includes(vegetable))?{
???????console.log(I?like?${vegetable});
???????//?condition?3:?must?be?large?quantity
???????if?(quantity?>=?10)?{
?????????console.log('I?have?bought?a?large?quantity');
???????}
?????}
???}?else?{
?????throw?new?Error('No?vegetable?from?the?list!');
???}
}
printVegetablesWithQuantity(null);?//??No?vegetable?from?the?list!
printVegetablesWithQuantity('cabbage');?//?I?like?cabbage
printVegetablesWithQuantity('cabbage',?20);?
//?'I?like?cabbage
//?'I?have?bought?a?large?quantity'
現(xiàn)在,我們有:
1 if/else 語句過濾非法條件 3 級嵌套if語句 (條件 1, 2, & 3)
一個普遍遵循的規(guī)則是:在非法條件匹配時提前退出。
function?printVegetablesWithQuantity(vegetable,?quantity)?{
??const?vegetables?=?['potato',?'cabbage',?'cauliflower',?'asparagus'];
???//?condition?1:?throw?error?early
???if?(!vegetable)?throw?new?Error('No?vegetable?from?the?list!');
???//?condition?2:?must?be?in?the?list
???if?(vegetables.includes(vegetable))?{
??????console.log(I?like?${vegetable});
?????//?condition?3:?must?be?a?large?quantity
??????if?(quantity?>=?10)?{
????????console.log('I?have?bought?a?large?quantity');
??????}
???}
}
通過這么做,我們少了一個嵌套層級。當(dāng)你有一個長的if語句時,這種代碼風(fēng)格特別好。
我們能通過條件倒置和提前返回,進(jìn)一步減少嵌套的if語句。查看下面的條件2,觀察我們是怎么做的
function?printVegetablesWithQuantity(vegetable,?quantity)?{
??const?vegetables?=?['potato',?'cabbage',?'cauliflower',?'asparagus'];
???if?(!vegetable)?throw?new?Error('No?vegetable?from?the?list!');?
???//?condition?1:?throw?error?early
???if?(!vegetables.includes(vegetable))?return;?
???//?condition?2:?return?from?the?function?is?the?vegetable?is?not?in?
??//??the?list?
??console.log(I?like?${vegetable});
??//?condition?3:?must?be?a?large?quantity
??if?(quantity?>=?10)?{
??????console.log('I?have?bought?a?large?quantity');
??}
}
通過倒置條件2,代碼沒有嵌套語句了。這種技術(shù)在我們有很多條件并且當(dāng)任何特定條件不匹配時,我們想停止進(jìn)一步處理的時候特別有用。
所以,總是關(guān)注更少的嵌套和提前返回,但也不要過度地使用。
3. 用對象字面量或Map替代Switch語句
讓我們來看看下面的例子,我們想要基于顏色打印水果:
function?printFruits(color)?{
??//?use?switch?case?to?find?fruits?by?color
??switch?(color)?{
????case?'red':
??????return?['apple',?'strawberry'];
????case?'yellow':
??????return?['banana',?'pineapple'];
????case?'purple':
??????return?['grape',?'plum'];
????default:
??????return?[];
??}
}
printFruits(null);?//?[]
printFruits('yellow');?//?['banana',?'pineapple']
上面的代碼沒有錯誤,但是它仍然有些冗長。相同的功能能用對象字面量以更清晰的語法實現(xiàn):
//?use?object?literal?to?find?fruits?by?color
??const?fruitColor?=?{
????red:?['apple',?'strawberry'],
????yellow:?['banana',?'pineapple'],
????purple:?['grape',?'plum']
??};
function?printFruits(color)?{
??return?fruitColor[color]?||?[];
}
另外,你也能用 Map 來實現(xiàn)相同的功能:
//?use?Map?to?find?fruits?by?color
??const?fruitColor?=?new?Map()
????.set('red',?['apple',?'strawberry'])
????.set('yellow',?['banana',?'pineapple'])
????.set('purple',?['grape',?'plum']);
function?printFruits(color)?{
??return?fruitColor.get(color)?||?[];
}
Map 允許保存鍵值對,是自從ES2015以來可以使用的對象類型。
對于上面的例子,相同的功能也能用數(shù)組方法Array.filter 來實現(xiàn)。
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?printFruits(color)?{
??return?fruits.filter(fruit?=>?fruit.color?===?color);
}
4. 默認(rèn)參數(shù)和解構(gòu)
當(dāng)使用 JavaScript 工作時,我們總是需要檢查 null/undefined 值并賦默認(rèn)值,否則可能編譯失敗。
function?printVegetablesWithQuantity(vegetable,?quantity?=?1)?{?
//?if?quantity?has?no?value,?assign?1
??if?(!vegetable)?return;
????console.log(We?have?${quantity}?${vegetable}!);
}
//results
printVegetablesWithQuantity('cabbage');?//?We?have?1?cabbage!
printVegetablesWithQuantity('potato',?2);?//?We?have?2?potato!
如果 vegetable 是一個對象呢?我們能賦一個默認(rèn)參數(shù)嗎?
function?printVegetableName(vegetable)?{?
????if?(vegetable?&&?vegetable.name)?{
?????console.log?(vegetable.name);
???}?else?{
????console.log('unknown');
???}
}
printVegetableName(undefined);?//?unknown
printVegetableName({});?//?unknown
printVegetableName({?name:?'cabbage',?quantity:?2?});?//?cabbage
在上面的例子中,如果vegetable 存在,我們想要打印 vegetable name, 否則打印"unknown"。
我們能通過使用默認(rèn)參數(shù)和解構(gòu)來避免條件語句 if (vegetable && vegetable.name) {} 。
//?destructing?-?get?name?property?only
//?assign?default?empty?object?{}
function?printVegetableName({name}?=?{})?{
???console.log?(name?||?'unknown');
}
printVegetableName(undefined);?//?unknown
printVegetableName({?});?//?unknown
printVegetableName({?name:?'cabbage',?quantity:?2?});?//?cabbage
因為我們只需要 name 屬性,所以我們可以使用 { name } 解構(gòu)參數(shù),然后我們就能在代碼中使用 name 作為變量,而不是 vegetable.name 。
我們還賦了一個空對象 {} 作為默認(rèn)值,因為當(dāng)執(zhí)行 printVegetableName(undefined) 時會得到一個錯誤:不能從 undefined 或 null 解構(gòu)屬性 name ,因為在 undefined 中沒有 name 屬性。
5. 用 Array.every & Array.some 匹配全部/部分內(nèi)容
我們能使用數(shù)組方法減少代碼行。查看下面的代碼,我們想要檢查是否所有的水果都是紅色的:
const?fruits?=?[
????{?name:?'apple',?color:?'red'?},
????{?name:?'banana',?color:?'yellow'?},
????{?name:?'grape',?color:?'purple'?}
??];
function?test()?{
??let?isAllRed?=?true;
??//?condition:?all?fruits?must?be?red
??for?(let?f?of?fruits)?{
????if?(!isAllRed)?break;
????isAllRed?=?(f.color?==?'red');
??}
??console.log(isAllRed);?//?false
}
這代碼太長了!我們能用 Array.every 來減少代碼行數(shù):
const?fruits?=?[
????{?name:?'apple',?color:?'red'?},
????{?name:?'banana',?color:?'yellow'?},
????{?name:?'grape',?color:?'purple'?}
??];
function?test()?{
??//?condition:?short?way,?all?fruits?must?be?red
??const?isAllRed?=?fruits.every(f?=>?f.color?==?'red');
??console.log(isAllRed);?//?false
}
相似地,如果我們想測試是否有任何紅色的水果,我們能用一行 Array.some 來實現(xiàn)它。
const?fruits?=?[
????{?name:?'apple',?color:?'red'?},
????{?name:?'banana',?color:?'yellow'?},
????{?name:?'grape',?color:?'purple'?}
];
function?test()?{
??//?condition:?if?any?fruit?is?red
??const?isAnyRed?=?fruits.some(f?=>?f.color?==?'red');
??console.log(isAnyRed);?//?true
}
6. 使用可選鏈和空值合并
這有兩個為編寫更清晰的條件語句而即將成為 JavaScript 增強(qiáng)的功能。當(dāng)寫這篇文章時,它們還沒有被完全支持,你需要使用 Babel 來編譯。
可選鏈允許我們沒有明確檢查中間節(jié)點是否存在地處理 tree-like 結(jié)構(gòu),空值合并和可選鏈組合起來工作得很好,以確保為不存在的值賦一個默認(rèn)值。
這有一個例子:
const?car?=?{
????model:?'Fiesta',
????manufacturer:?{
????name:?'Ford',
????address:?{
????????street:?'Some?Street?Name',
????????number:?'5555',
????????state:?'USA'
??????}
????}
}?
//?to?get?the?car?model
const?model?=?car?&&?car.model?||?'default?model';
//?to?get?the?manufacturer?street
const?street?=?car?&&?car.manufacturer?&&?car.manufacturer.address?&&?
car.manufacturer.address.street?||?'default?street';
//?request?an?un-existing?property
const?phoneNumber?=?car?&&?car.manufacturer?&&?car.manufacturer.address?
&&?car.manufacturer.phoneNumber;
console.log(model)?//?'Fiesta'
console.log(street)?//?'Some?Street?Name'
console.log(phoneNumber)?//?undefined
所以,如果我們想要打印是否車輛生產(chǎn)商來自美國,代碼將看起來像這樣:
const?isManufacturerFromUSA?=?()?=>?{
???if(car?&&?car.manufacturer?&&?car.manufacturer.address?&&?
?car.manufacturer.address.state?===?'USA')?{
?????console.log('true');
???}
}
checkCarManufacturerState()?//?'true'
你能清晰地看到當(dāng)有一個更復(fù)雜的對象結(jié)構(gòu)時,這能變得多亂。有一些第三方的庫有它們自己的函數(shù),像 lodash 或 idx。例如 lodash 有 _.get 方法。然而,JavaScript 語言本身被引入這個特性是非常酷的。
這展示了這些新特性如何工作:
//?to?get?the?car?model
const?model?=?car?.model????'default?model';
//?to?get?the?manufacturer?street
const?street?=?car?.manufacturer?.address?.street????'default?street';
//?to?check?if?the?car?manufacturer?is?from?the?USA
const?isManufacturerFromUSA?=?()?=>?{
???if(car?.manufacturer?.address?.state?===?'USA')?{
?????console.log('true');
???}
}
這看起來很美觀并容易維護(hù)。它已經(jīng)到 TC39 stage 3 階段,讓我們等待它獲得批準(zhǔn),然后我們就能無處不在地看到這難以置信的語法的使用。
總結(jié)
讓我們?yōu)榱司帉懜逦?、易維護(hù)的代碼,學(xué)習(xí)并嘗試新的技巧和技術(shù),因為在幾個月后,長長的條件看起來像搬石頭砸自己的腳。?
