本文是一篇翻譯文章。
原文鏈接: https://dev.to/hellomeghna/tips-to-write-better-conditionals-in-javascript-2189
什么是條件語句
不管是什么編程語言橄霉,代碼中都少不了做判斷誓篱,以及依據(jù)一個輸入的條件決定要執(zhí)行不同的操作姿骏。
例如,在一款游戲中摧玫,如果玩家的生命數(shù)小于0,游戲就宣告結(jié)束稚新。在一款天氣預(yù)報app中,在早上顯示太陽的圖片跪腹,在夜間顯示星星和月亮的圖片褂删。在本文中我們就將介紹如何在JavaScript中進(jìn)行類似的條件處理。
當(dāng)你寫JavaScript時冲茸,有時候會寫出一大堆包含許多條件判斷的代碼屯阀。這些條件語句,在開始的時候可能還比較好理解轴术,但是過一陣子之后就變得一團(tuán)糟了蹲盘。其實有比if/else
更棒的方式去實現(xiàn)條件判斷。
這里就有一些關(guān)于如何寫出干凈優(yōu)雅的條件判斷的建議膳音。
目錄
- Array.includes
- 盡早退出和返回
- 用Object遍歷 或者 Map 取代 Switch 表達(dá)式
- 使用默認(rèn)參數(shù) 和 解構(gòu)賦值
- 使用Array.every 和 Array.some去實現(xiàn)所有 和 部分條件判斷
- Use Optional Chaining and Nullish Coalescing
1.Array.includes
如果有多個條件可以使用Array.includes
例如:
function printAnimals(animal) {
if (animal === 'dog' || animal === 'cat') {
console.log(`I have a ${animal}`);
}
}
console.log(printAnimals('dog')); // I have a dog
上面的代碼似乎看起來還行,那是因為我們只需要檢測兩種小動物铃诬。然而我們并不確定用戶會輸入什么祭陷。如果動物的類型變多了呢苍凛?如果我們繼續(xù)通過擴(kuò)展||
條件判斷來滿足需求,我們的代碼會變得越來越難維護(hù)兵志,并且看起來亂亂的醇蝴。
解決方案:
我們可以用 Array.includes
來重構(gòu)一下上面的代碼 :
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)在悠栓,如果我們想繼續(xù)擴(kuò)充條件,我們只需要往數(shù)組里添加新的元素就可以了按价。(清晰多了呢)
2.盡早退出和返回
這是一個非巢咽剩酷的小技巧,可以使你的代碼看起來簡潔楼镐。我記得我從工作的第一天起癞志,我就被教導(dǎo),在條件判斷時要early exit
(盡早退出)框产。
讓我們?yōu)樯弦粋€示例多添加些條件凄杯。如果animal 不再是一個簡單的string
了,而是一個有特定屬性的object
秉宿。
所以現(xiàn)在需求變成了下面這樣:
- 如果沒有animal戒突,拋出一個錯誤
- 打印出animal的類型
- 打印出animal的名字
- 打印出animal的類型
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'
對于上面的代碼,你怎么看呢描睦?
上面的代碼沒什么bug膊存,但是看起來太長了,而且很難維護(hù)呢酌摇。一個新人可能得花個一上午來找哪些括號是一對的呢(手動滑稽)膝舅。如果邏輯再復(fù)雜點呢,if/else
就更多了窑多。我們可以用?:
仍稀,$$
運(yùn)算符等來重構(gòu)上面的代碼。但是埂息,我就不(哈哈哈……)技潘。我使用了多次return來重構(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)賦值和函數(shù)參數(shù)的默認(rèn)值享幽。默認(rèn)值的作用是,如果我們沒有傳參(undifined)拾弃,也能保證不會報錯值桩。
另一個例子:
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
接下來我要介紹我們的套路了—— 當(dāng)遇到不可用的條件時盡早退出函數(shù)
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');
}
}
}
這樣重構(gòu)后豪椿,我們就少了一層if
嵌套奔坟,當(dāng)你的條件判斷比較長時携栋,這種代碼風(fēng)格尤為好用。
我們能進(jìn)一步減少if
嵌套咳秉,通過對條件進(jìn)行取反婉支,然后return
。下面就是具體實現(xiàn):
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');
}
}
通過對第二個條件取反澜建,代碼里再也看不到if
的嵌套了向挖。這種技巧適用于當(dāng)我們有好多條件判斷,并且當(dāng)滿足某一個時炕舵,不再進(jìn)行剩余的邏輯處理何之。
因此,我們的目標(biāo)是消滅嵌套幕侠,及早return帝美。但是return大法好,也不能"貪杯"啊~
3. 用Object遍歷 或者 Map 取代 Switch 表達(dá)式
讓我們看下這個例子晤硕,我們想基于顏色打印出水果:
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']
上面的代碼沒什么錯誤悼潭,就是看起來有點長。我們可以用Object來實現(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] || [];
}
當(dāng)然也可以用Map
// 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 (Es6)的語法舞箍,大家注意兼容性呀舰褪!
也能用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)賦值
我們寫JavaScript時,經(jīng)常需要去檢查null/undefined
疏橄,并對參數(shù)賦默認(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)賦值么晃酒?
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有可用的值窄绒,我們就打印出它的name贝次,否則打印unknow
。
我們可以用默認(rèn)值 和 解構(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)出來,然后我們就可以使用name
變量了位谋,這樣就不需要使用vegetable.name
了山析。
我們也給函數(shù)的參數(shù)設(shè)置了一個默認(rèn)值{}
,否則當(dāng)我們執(zhí)行printVegeTable(undefined)
的時就會報錯Cannot destructure property name of undefined or null
掏父,因為undefined
不是對象笋轨,是不能解構(gòu)的。
5.使用Array.every 和 Array.some去實現(xiàn)所有 和 部分條件判斷
我么可以使用數(shù)組的這些方法來減少代碼行數(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試試:
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. Use Optional Chaining and Nullish Coalescing
這兩個功能在JavaScript中是非常有用的组哩。但是目前支持力度還不是很好等龙,所以需要使用babel進(jìn)行編譯。
Optional chaining
使我們可以跳過中間層級去檢查一個樹狀解構(gòu)是否包含某個屬性伶贰。nullish coalescing
可以和
Optional chaining
配合使用蛛砰,來為變量賦默認(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
所以如果我們想一輛汽車的制造商是否是美國黍衙,我們必須這么寫代碼:
const isManufacturerFromUSA = () => {
if(car && car.manufacturer && car.manufacturer.address &&
car.manufacturer.address.state === 'USA') {
console.log('true');
}
}
checkCarManufacturerState() // 'true'
你能看到泥畅,這么寫代碼是多么的凌亂。早已經(jīng)有一些第三方庫琅翻,像lodash
或者idx
有自己的函數(shù)位仁,去簡化這個操作。例如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
提案中了。我們再等等就可以用上了闸拿。
總結(jié)
讓我們試著使用這些建議來寫一些干凈易于維護(hù)的代碼吧空盼,因為那些冗長的條件判斷,再過幾個月之后連你自己都看不懂了新荤。