1. 使用 Array.includes 來處理多重條件
function test(fruit) { if (fruit === "apple" || fruit === "strawberry") { console.log("red fruit"); } }
|
乍看之下,寫法沒什麼錯誤。可是當我們有更多紅色水果的選項時,如 cherry
(櫻桃)和 cranberries
(蔓越莓),難道我們要增加更多的 ||
邏輯運算子來判斷?
我們用 Array.includes 來改寫一次上面的判斷式:
function test(fruit) { const redFruits = ["apple", "strawberry", "cherry", "cranberries"]; if (redFruits.includes("apple")) { console.log("red fruit"); } }
|
將選項、可能的答案提取出來,放入陣列 red fruits
當中。這樣寫的話,程式碼看起來更加簡潔。
2. 減少巢狀,儘早回傳( return)
這裏使用上面的程式碼內容來接續下面的範例,新增兩個條件。
- 如果參數
fruit
沒有值,回傳錯誤( error
)。 - 如果數量超過 10 的話,印出一個訊息。
function test(fruit, quantity) { const redFruits = ["apple", "strawberry", "cherry", "cranberries"];
if (fruit) { if (redFruits.includes(fruit)) { console.log("red fruit");
if (quantity > 10) { console.log("big quantity"); } } } else { throw new Error("No fruit!"); } }
test(null); test("apple"); test("apple", 20);
|
以上程式碼,已經加入了:
- 1 個
if/else
的條件判斷,篩選掉無效的內容。 - 3 個巢狀條件判斷(判定一、判定二、判定三)。
對我個人而言,有一個基本通則,那就是「減少巢狀,儘早回傳」:
function test(fruit, quantity) { const redFruits = ["apple", "strawberry", "cherry", "cranberries"];
if (!fruit) throw new Error("No fruit!");
if (redFruits.includes(fruit)) { console.log("red fruit");
if (quantity > 10) { console.log("big quantity"); } } }
|
改寫程式碼後,現在只有一層的巢狀判斷式。尤其當你有很長的判斷條件時,這樣的 Coding Style
很棒(想像一下,你捲軸需要捲到很後面才知道 else 區塊做了什麼處理。難以閱讀,不帥!)
透過轉換判斷式寫法、儘早回傳,可以更進一步的精簡巢狀 if
,來看下面 判斷式二
是如何實現的:
function test(fruit, quantity) { const redFruits = ["apple", "strawberry", "cherry", "cranberries"];
if (!fruit) throw new Error("No fruit!");
if (!redFruits.includes(fruit)) return;
console.log("red");
if (quantity > 10) { console.log("big quantity"); } }
|
將判斷式二改成上面的寫法,我們的程式碼就不再這麼「巢」了!假設我們有很長的程式邏輯,並且想要在不滿足條件時中斷程式的進行,這樣的寫法很有效率、易讀。
然而,其實也沒什麼硬性規定該如何寫,一切依使用情境而定,問問自己:「這個寫法套用在目前的狀況有增加可讀性嗎?」
對我來說,我應該會選擇寫法二(只有一層的巢狀結構),原因在於:
- 程式碼簡潔、直覺,有一層的
if
判定使得邏輯更清晰 - 反轉程式碼在讀的時候會產生思考流程、增加認知負擔
因此,「減少巢狀,儘早回傳」是原則,但也不要過度使用。
以下有針對這個主題討論的文章及 StackOverflow 討論,有興趣的話可以看一下:
3. 函式使用預設值、解構
我猜你應該很熟悉下面這段程式碼,因為我們總是在確認 null
/ undefined
的值,是的話就給一個預設值。
function test(fruit, quantity) { if (!fruit) return;
const q = quantity || 1; console.log(`We have ${q} ${fruit}!`); }
test("banana"); test("apple", 2);
|
事實上,在 function 針對參數(parameter) q
先設定預設值,就可以不用在程式碼區塊定義 變數 q
。
function test(fruit, quantity = 1) { if (!fruit) return; console.log(`We have ${q} ${fruit}!`); }
test("banana"); test("apple", 2);
|
更簡單、直覺了不是嗎?記得函式都可以設定每個參數的預設值,所以我們也可以幫參數 fruit
設定預設值,如 function test(fruit = 'unknown', quantity = 1)
。
那假設 fruit
是物件怎麼辦?
function test(fruit) { if (fruit && fruit.name) { console.log(fruit.name); } else { console.log("unknown"); } }
test(undefined); test({}); test({ name: "apple", color: "red" });
|
上面的範例中,我們希望水果名稱有值時就把它印出來,沒值就印出 unknown
。其實,我們可以透過設定函式預設值與解構來減少 fruit && fruit.name
這種條件判定。
function test({ name } = {}) { console.log(name || "unknown"); }
test(undefined); test({}); test({ name: "apple", color: "red" });
|
因為函式只需要用到物件 fruit
的屬性 name
,所以我們可透過 {name}
將其解構出來使用,函式當中就可以拿 name
當作變數來參照,取代 fruit.name
寫法。
我們解構賦值時,至少要設定空物件 {}
為預設值。否則會出現這個錯誤:Uncaught TypeError: Cannot destructure property
nameof 'undefined' or 'null'.
。
如果你不介意使用第三方函式庫的話,那麼我這裡有兩個方法可減少 null
的檢查:
Lodash get function:
_.get(object, path, [defaultValue]);
|
Lodash 使用方法:
function test(fruit) { console.log(_.get(fruit, "name", "unknown")); }
test(undefined); test({}); test({ name: "apple", color: "red" });
|
你可以在 Codepen 玩玩這段程式碼。如果你是 Functional Programming 愛好者,你可以把 Library 選項改成 Lodash fp(方法會改成 ._getOr
),引數順序會從 a, b, c
改成 c, b, a
。這裡有我的 FP v4.0 - demo。
Lodash FP 使用方法:
function test(fruit) { console.log(_.getOr("unknown", "name", fruit)); }
test(undefined); test({}); test({ name: "apple", color: "red" });
|
4. 相較於 Switch,Map / Object 是個好選擇
下面範例想要根據水果顏色,印出水果:
function test(color) { switch (color) { case "red": return ["apple", "strawberry"]; case "yellow": return ["banana", "pineapple"]; case "purple": return ["grape", "plum"]; default: return []; } }
test(null); test("yellow");
|
程式碼邏輯雖沒錯,卻非常冗贅。相同的結果,可以利用物件實體語法(object literal),以清楚的語句來達成。
const fruitColor = { red: ["apple", "strawberry"], yellow: ["banana", "pineapple"], purple: ["grape", "plum"] };
function test(color) { return fruitColor[color] || []; }
test(null); test("yellow");
|
另一個選擇是 Map 達成相同結果:
const fruitColor = new Map() .set("red", ["apple", "strawberry"]) .set("yellow", ["banana", "pineapple"]) .set("purple", ["grape", "plum"]);
function test(color) { return fruitColor.get(color) || []; }
test(null); test("yellow");
|
Map 是 ES2015 起來有的物件型別,讓你可以去儲存成對的 key 及 value。
那麼,難道我們該捨棄使用 Switch 嗎?別讓自己受限,就我個人而言,我會盡可能地使用物件實體語法(object literal),但我不會把規則定死,老話一句,一切只要適用於情境即可。
Todd Motto 寫過一篇文章在討論 switch 和 物件實體語法(object literal),有興趣可以看一下。
TL; DR; 重構語法
重構資料結構後,可以透過 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 test(color) { return fruits.filter(f => f.color === color); }
|
總是有超過一種方式可以達成相同結果,以上已經提到 4 種程式範例,寫程式真的很有趣吧,呵呵。
5. 用 Array.every 和 Array.some 應用於全部與局部條件
最後一個技巧是利用 JavaScript 提供 Array 的新方法(但也不是那麼新啦)來減少程式碼的行數。來看看以下的程式碼,我們來檢查全部的水果是不是紅色的?
檢查全部水果是否都是紅色的?
const fruits = [ { name: "apple", color: "red" }, { name: "banana", color: "yellow" }, { name: "grape", color: "purple" } ];
function test() { let isAllRed = true;
for (let f of fruits) { if (!isAllRed) break; isAllRed = f.color === "red"; }
console.log(isAllRed); }
|
這樣的程式碼太長了,可以使用 Array.every
來減少程式碼:
const fruits = [ { name: "apple", color: "red" }, { name: "banana", color: "yellow" }, { name: "grape", color: "purple" } ];
function test() { const isAllRed = fruits.every(f => f.color === "red");
console.log(isAllRed); }
|
更清楚了吧?另一個相似的做法,如果我們想要檢查任一種水果是不是紅色的,可以用 Array.some
一行解決!
檢查任一種水果是紅色的?
const fruits = [ { name: "apple", color: "red" }, { name: "banana", color: "yellow" }, { name: "grape", color: "purple" } ];
function test() { const isAnyRed = fruits.some(f => f.color === "red");
console.log(isAnyRed); }
|
結論
我們一起寫出更多易讀的程式吧,希望你看完這篇文章能有所獲。
那就這樣囉,Happy Coding!
覺得文章還不賴的話,訂閱我的 Twitter
留言
張貼留言