JavaScript 的 switch 有四樣寫法,你知道麼?不管你知道不知道,反正我是不知道。
我所知道的 JavaScript 的 switch 語句只有一種寫法。但要說到對分支的處理,寫法可就多了去了。if 分支寫法可以算一種,switch 分支寫法可以算第二種,第三種是使用策略模式,如果要把條件運算子也算上的話,嗯,剛好四種。
不過本文的主角是 switch。大家都瞭解 switch 的寫法一般來說是 switch 變數或表示式,case 常量。嗯,比如說,一個百分制成績,90 及 90 分以上算優秀,80 及以上 90 以下算良好,60 及以上 80 以下算合格,60 以下為不合格,用 switch 大概會這麼寫:
function calcGrade(score) {
const line = score / 10 | 0;
switch (line) {
case 10: case 9:
return "優秀";
case 8:
return "良好";
case 7: case 6:
return "合格";
default:
return "不合格";
}
}
程式碼中score / 10 | 0
和Math.floor(score / 10)
是一樣的效果,就是除以 10 取商的整數部分。
這段 switch 用得中規中矩,用取整的辦法來避免使用一長串 if ... else 分支也算是取了巧。
但是現在規則改了,將合格和良好的分隔點從 80 分降到 75 分,該怎麼辦?
按上面取整的辦法依然可以,不過這次除數不再是 10,而是 5。相應地,case 也多了很多:
- 18、19、20 是優秀
- 15、16、17 是良好
- 12、13、14 是合格
- 剩下的是不合格
寫 9 個 case,真不如用 if ... else 算了。
是嗎?其實用 switch 也有簡單一些的寫法:
function calcGrade(score) {
switch (true) {
case score >= 90:
return "優秀";
case score >= 75:
return "良好";
case score >= 60:
return "合格";
default:
return "不合格";
}
}
是不是感覺有些奇怪?這完全不是習慣了的 switch 表示式 case 常量,而是正好相反,switch 常量 case 表示式!如果你拿這段程式去跑一下,會發現一點問題都沒有。因為——switch 和 case 是按 ===
來匹配的,它並不在乎是表示式還是常量,或者說,switch 和 case 後面都可以接表示式!
是的,表示式!
所以上面那個示例中,把 switch(true)
改成 switch( 2 > 1)
也是一樣的效果。
好啦,腦洞已開。switch 到底有多少種寫法已經不重要了。接下來要研究的是 switch 的變種 ——
看到 C# 有 switch 表示式,眼饞,能實現嗎?
不用眼饞,JavaScript 裡一切都可以是表示式 …… 如果不是,用 IIFE 封裝一個就是了
function calcGrade(score) {
return (value => {
switch (true) {
case value >= 90:
return "優秀";
case value >= 75:
return "良好";
case value >= 60:
return "合格";
default:
return "不合格";
}
})(score);
}
注意這裡把 score
作為 IIFE 的引數,是因為在實際使用中,可能需要傳入的是一個表示式。這種情況下應該提前求值,而且只求一次(避免替在的副作用)。
不過這樣的封裝顯然沒什麼意義,如果真要這樣封裝,不如封成策略:
function calcGrade(score) {
return ((value, rules) => rules.find(({ t }) => t(value)).v)(
score,
[
{ t: n => n >= 90, v: "優秀" },
{ t: n => n >= 75, v: "良好" },
{ t: n => n >= 60, v: "合格" },
{ t: () => true, v: "不合格" },
]
);
}
每項策略都是一個含有 tester (t
) 和值 (v
) 的物件。tester 是一個判斷函式,傳入需要判斷的值,也就是 switch (表示式)
這裡表示式,而這個表示式也是提前求值之後作為 IIFE 的引數傳入的。應用策略的過程簡單粗暴,就是找到第一個符合條件的策略,把它的值取出來。
當然這樣用策略有點大材小用。真正需要用策略的情況,策略中通常不是一個值,而是一個行為,也就函式。
我們知道在 switch 語句中,各個 case 之間是在同一個作用域內,所以不能在兩個 case 語句中宣告同一個區域性變數。雖然用 { }
包裹可以解決這些問題,但程式碼看起來不怎麼好看,特別是還要注意不要忘了 break
。如果用策略的話,看起來可能會順眼一眼,也不用擔心 break 的問題:
這裡為了演示,在策略行為中將先輸出成績,再返回等級。
function calcGrade(score) {
return ((value, rules) => rules.find(({ t }) => t(value)).fn(value))(
score,
[
{
t: n => n >= 90,
fn: score => {
const grade = "優秀";
console.log(grade, score);
return grade;
}
},
{
t: n => n >= 75,
fn: score => {
const grade = "良好";
console.log(grade, score);
return grade;
}
},
{
t: n => n >= 60,
fn: score => {
const grade = "合格";
console.log(grade, score);
return grade;
}
},
{
t: () => true,
fn: score => {
const grade = "不合格";
console.log(grade, score);
return grade;
}
},
]
);
}
程式碼確實有點長,因為有策略行為邏輯在裡面。如果真的是要當作 switch 表示式來用的話,策略部分應該是一個表示式,不會太長的。上面的程式碼中,策略行為相似,可以封裝成一個函式,這樣就能寫成表示式的形式了:
function calcGrade(score) {
const printGrade = (grade, score) => {
console.log(grade, score);
return grade;
};
return ((value, rules) => rules.find(({ t }) => t(value)).fn(value))(
score,
[
{ t: n => n >= 90, fn: score => printGrade("優秀", score) },
{ t: n => n >= 75, fn: score => printGrade("良好", score) },
{ t: n => n >= 60, fn: score => printGrade("合格", score) },
{ t: () => true, fn: score => printGrade("不合格", score) },
]
);
}
現在看起來是不是像樣了?
上面的程式碼形式各異,乾的事情都差不多,也沒有誰優誰劣的比較。看得順眼怎麼都優雅,看不順眼怎麼都不受寵。在不同的情況下,選用合適的做法就好。上面的程式碼使用的 find()
來查詢策略,如果改用 filter()
,那又會是另一番景象了 ~(~ ̄▽ ̄)~
。