使用 JavaScript 編寫更好的條件語句
在任何編程語言中,代碼需要根據不同的條件在給定的輸入中做不同的決定和執行相應的動作。
例如,在一個游戲中,如果玩家生命點為0,游戲結束。在天氣應用中,如果在早上被查看,顯示一個日出圖片,如果是晚上,則顯示星星和月亮。在這篇文章中,我們將探索JavaScript中所謂的條件語句如何工作。
如果你使用JavaScript工作,你將寫很多包含條件調用的代碼。條件調用可能初學很簡單,但是還有比寫一對對if/else更多的東西。這里有些編寫更好更清晰的條件代碼的有用提示。
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
上面的代碼看起來很好因為我們只檢查了兩個動物。然而,我們不確定用戶輸入。如果我們要檢查任何其他動物呢?如果我們通過添加更多“或”語句來擴展,代碼將變得難以維護和不清晰。
解決方案:
我們可以通過使用 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
這里,我們創建來一個動物數組,所以條件語句可以和代碼的其余部分抽象分離出來?,F在,如果我們想要檢查任何其他動物,我們只需要添加一個新的數組項。
我們也能在這個函數作用域外部使用這個動物數組變量來在代碼中的其他任意地方重用它。這是一個編寫更清晰、易理解和維護的代碼的方法,不是嗎?
2. 提前退出 / 提前返回
這是一個精簡你的代碼的非??岬募记?。我記得當我開始專業工作時,我在第一天學習使用提前退出來編寫條件。
讓我們在之前的例子上添加更多的條件。用包含確定屬性的對象替代簡單字符串的動物。
現在的需求是:
-
如果沒有動物,拋出一個異常
-
打印動物類型
-
打印動物名字
-
打印動物性別
- 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'
你覺得上面的代碼怎么樣?
它工作得很好,但是代碼很長并且維護困難。如果不使用lint工具,找出閉合花括號在哪都會浪費很多時間。:smile: 想象如果代碼有更復雜的邏輯會怎么樣?大量的if..else語句。
我們能用三元運算符、&&條件等語法重構上面的功能,但讓我們用多個返回語句編寫更清晰的代碼。
- 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'
在這個重構過的版本中,也包含了解構和默認參數。默認參數確保如果我們傳遞undefined作為一個方法的參數,我們仍然有值可以解構,在這里它是一個空對象{}。
通常,在專業領域,代碼被寫在這兩種方法之間。
另一個例子:
- 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'
現在,我們有:
-
1 if/else 語句過濾非法條件
-
3 級嵌套if語句 (條件 1, 2, & 3)
一個普遍遵循的規則是:在非法條件匹配時提前退出。
- 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');
- }
- }
- }
通過這么做,我們少了一個嵌套層級。當你有一個長的if語句時,這種代碼風格特別好。
我們能通過條件倒置和提前返回,進一步減少嵌套的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,代碼沒有嵌套語句了。這種技術在我們有很多條件并且當任何特定條件不匹配時,我們想停止進一步處理的時候特別有用。
所以,總是關注更少的嵌套和提前返回,但也不要過度地使用。
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']
上面的代碼沒有錯誤,但是它仍然有些冗長。相同的功能能用對象字面量以更清晰的語法實現:
- // 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
來實現相同的功能:
- // 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以來可以使用的對象類型。
對于上面的例子,相同的功能也能用數組方法 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 printFruits(color) {
- return fruits.filter(fruit => fruit.color === color);
- }
4. 默認參數和解構
當使用 JavaScript 工作時,我們總是需要檢查 null/undefined
值并賦默認值,否則可能編譯失敗。
- 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 是一個對象呢?我們能賦一個默認參數嗎?
- 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"。
我們能通過使用默認參數和解構來避免條件語句 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 }
解構參數,然后我們就能在代碼中使用 name
作為變量,而不是 vegetable.name
。
我們還賦了一個空對象 {} 作為默認值,因為當執行 printVegetableName(undefined)
時會得到一個錯誤:不能從 undefined
或 null
解構屬性 name
,因為在 undefined
中沒有 name
屬性。
5. 用 Array.every & Array.some 匹配全部/部分內容
我們能使用數組方法減少代碼行。查看下面的代碼,我們想要檢查是否所有的水果都是紅色的:
- 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
來實現它。
- 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 增強的功能。當寫這篇文章時,它們還沒有被完全支持,你需要使用 Babel 來編譯。
可選鏈允許我們沒有明確檢查中間節點是否存在地處理 tree-like 結構,空值合并和可選鏈組合起來工作得很好,以確保為不存在的值賦一個默認值。
這有一個例子:
- 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'
你能清晰地看到當有一個更復雜的對象結構時,這能變得多亂。有一些第三方的庫有它們自己的函數,像 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');
- }
- }
這看起來很美觀并容易維護。它已經到 TC39 stage 3 階段,讓我們等待它獲得批準,然后我們就能無處不在地看到這難以置信的語法的使用。
總結
讓我們為了編寫更清晰、易維護的代碼,學習并嘗試新的技巧和技術,因為在幾個月后,長長的條件看起來像搬石頭砸自己的腳。