if else 與 switch case 是最基本的邏輯判斷方式,但卻也是 複雜度 的元兇,實務上善用 ECMAScript 語言特性與 Higher Order Function 降低複雜度,讓程式碼可讀性更高,也更容易維護。
Version
macOS Catalina 10.15.1
VS Code 1.40.1
Quokka 1.0.259
ECMAScript 2015+
&&
let fn = val => {
if (val === 'iPhone')
return val;
else
return false;
}
fn() // ?
fn('iPhone') // ?
若為 true 則直接回傳,否則回傳 false。

let fn = val => val === 'iPhone' && val
fn() // ?
fn('iPhone') // ?
可使用 &&,若為 false 直接回傳 false,若為 true 才會繼續執行 && 右側部分。

||
let fn = val => {
if (val === 'iPhone')
return true;
else
return val;
}
fn() // ?
fn('iPad') // ?
fn('iPhone') // ?
若需求反過來,只有 false 直接回傳,否則回傳 true。

let fn = val => val === 'iPhone' || val
fn() // ?
fn('iPad') // ?
fn('iPhone') // ?
可使用 ||,若為 true 直接回傳 true,若為 false 才會繼續執行 || 右側部分。

Or
let fn = val => {
if (val === 'iPhone' || val === 'iPad' || val === 'Apple Watch')
return true
else
return false
}
fn('Apple Watch') // ?
fn('Macbook') // ?
常見的需求,|| 若其中一個條件成立就回傳 true,否則回傳 false。

let fn = val => ['iPhone', 'iPad', 'Apple Watch'].includes(val)
fn('Apple Watch') // ?
fn('Macbook') // ?
Array.prototype.includes() 的原意是判斷 item 是否在 array 中,若有則回傳 true,否則傳回 false。
利用 includes() 這個特性,可將所有要判斷的項目改寫在 array 中,改用 includes() 判斷,不只清爽且可讀性也很高。

let fn = val => ['iPhone', 'iPad', 'Apple Watch'].some(x => x === val)
fn('Apple Watch') // ?
fn('Macbook') // ?
也可使用 Array.prototype.some(),includes() 與 some() 的差異是 includes() 的 argument 為 value,而 some() 為 function。

Guard Clause
let fn = (product, quantity) => {
let apples = ['iPhone', 'iPad', 'Apple Watch']
if (product) {
if (apples.includes(product)) {
console.log(`${product} is Apple product`)
if (quantity > 10)
console.log('big quantity')
}
} else
throw new Error('No Apple product')
}
fn('iPhone')
fn('iPad', 20)
fn()
若都使用 正向判斷,可能會造成 nested if else 而難以維護。

let fn = (product, quantity) => {
let apples = ['iPhone', 'iPad', 'Apple Watch']
if (!product) throw new Error('No Apple product');
if (!apples.includes(product)) return
console.log(`${product} is Apple product`)
if (quantity > 10) console.log('big quantity')
}
fn('iPhone')
fn('iPad', 20)
fn()
可使用 guard clause 將反向邏輯提早 return,讓所有 if 判斷都扁平化只有一層,這也是為什麼有些 Lint 會警告你不能寫 else,因為使用 guard clause 之後,就不會再出現 else 了。

Array.prototype.every()
let data = [
{ name: 'iPhone', color: 'white' },
{ name: 'iPad', color: 'black' },
{ name: 'Macbook', color: 'silver' }
]
let fn = val => arr => {
let result = true
for (let x of arr) {
if (!result) break
result = x.color === val
}
return result
}
fn('white')(data) // ?
若要 全部條件 都成立,實務上我們也會將資料與條件全部先放在 array 中。
最直覺方式就是透過 for loop 判斷。

let data = [
{ name: 'iPhone', color: 'white' },
{ name: 'iPad', color: 'black' },
{ name: 'Macbook', color: 'silver' }
]
let fn = val => arr => arr.every(x => x.color === val)
fn('white')(data) // ?
也可使用 Array.prototype.every(),則所有條件都為 true 才會回傳 true。

Array.prototype.some()
let data = [
{ name: 'iPhone', color: 'white' },
{ name: 'iPad', color: 'black' },
{ name: 'Macbook', color: 'silver' }
]
let fn = val => arr => {
let result = false
for (let x of arr) {
if (result) break
result = x.color === val
}
return result
}
fn('white')(data) // ?
若只要 有一個條件 成立即可,實務上我們也會將資料與條件全部先放在 array 中。
最直覺方式就是透過 for loop 判斷。

let data = [
{ name: 'iPhone', color: 'white' },
{ name: 'iPad', color: 'black' },
{ name: 'Macbook', color: 'silver' }
]
let fn = val => arr => arr.some(x => x.color === val)
fn('white')(data) // ?
若只要 有一個條件 成立即可,可使用 Array.prototype.some()。
也可使用 Array.prototype.some(),只要 有一個條件 成立就回傳 true。

Default Parameter
let fn = (product, quantity) => {
if (!product) return
quantity = quantity || 1
return `We have ${quantity} ${product}!`
}
fn() // ?
fn('iPad') // ?
fn('iPhone', 2) // ?
ES5 function 並沒有提供 default parameter,因此會透過判斷是否為 undefined 與 || 小技巧設定預設值。
但由於
0、''與false也視為 falsy value,若你的需求是能接受0、''與false,就不能使用此技巧

let fn = (product, quantity = 1) => {
if (!product) return
return `We have ${quantity} ${product}!`
}
fn() // ?
fn('iPad') // ?
fn('iPhone', 2) // ?
ES6 提供了 default parameter 後,語意更加清楚,也不用判斷 undefined 了。

Object Destructing
let fn = product => {
if (product && product.name)
return product.name
else
return 'unknown'
}
fn() // ?
fn({}) // ?
fn({ name: 'iPhone', color: 'white' }) // ?
若 parameter 為 object,在 ES5 為避免 parameter 為 undefined 或 null,又避免根本無 property,必須小心翼翼的判斷 object 與 property。

let fn = ({ name } = {}) => name || 'unknown'
fn() // ?
fn({}) // ?
fn({ name: 'iPhone', color: 'white' }) // ?
透過 ES6 的 default parameter 與 object destructing,可一次解決判斷 object 與 property 問題。
- 當 parameter 為
undefined時,會使用預設值{} - 當 object 沒有 property 時, Object Destructing 拆解後為
undefined,都是unknown - 否則會正常取得 property 值

If / else if / else
let sendLog = val => console.log(val)
let jumpTo = val => console.log(`Jump to ${val}`)
let fn = status => {
if (status === 1) {
sendLog('processing')
jumpTo('index')
} else if (status === 2) {
sendLog('fail')
jumpTo('error')
} else {
sendLog('others')
jumpTo('default')
}
};
fn(1)
實務上常會遇到 if ... else if ... else 都呼叫相同的 function,只是 argument 不相同。

Switch
let sendLog = val => console.log(val)
let jumpTo = val => console.log(`Jump to ${val}`)
let fn = status => {
switch(status) {
case 1:
sendLog('processing')
jumpTo('index')
break
case 2:
sendLog('fail')
jumpTo('error')
break
default:
sendLog('others')
jumpTo('default')
break
}
}
fn(1)
整理成 switch() 後 ,可讀性稍可。

Object
let sendLog = val => console.log(val)
let jumpTo = val => console.log(`Jump to ${val}`)
let actions = {
'1': ['processing', 'index'],
'2': ['fail', 'error'],
'default': ['others', 'default']
}
let fn = status => {
let [logName, pageName] = actions[status] || actions['default']
sendLog(logName)
jumpTo(pageName)
}
fn(1)
可將 switch 的判斷值整理成 object 的 key,function 的 argument 以 array 成為 object 的 value,如此則可使用 object 的 [] 取代 switch,default 值則以 || 實現。
object 的回傳值還可使用 array destructuring 加以拆解。

Nested If
let jumpTo = val => console.log(`Jump to ${val}`)
let fn = (identity, status) => {
if (identity === 'admin') {
if (status === 1) jumpTo('admin_1')
else if (status === 2) jumpTo('admin_2')
else jumpTo('all_default')
} else if (identity === 'member') {
if (status === 1) jumpTo('member_1')
else if (status === 2) jumpTo('member_2')
else jumpTo('all_default')
}
else jumpTo('all_default')
}
fn('admin', 1)
fn('admin', 3)
巢狀 if 也是常見複雜度怪獸。

Object
let jumpTo = val => console.log(`Jump to ${val}`)
let admin_1 = () => jumpTo('admin_1')
let admin_2 = () => jumpTo('admin_2')
let member_1 = () => jumpTo('member_1')
let member_2 = () => jumpTo('member_2')
let all_default = () => jumpTo('all_default')
let actions = {
admin_1,
admin_2,
member_1,
member_2,
all_default
}
let fn = (identity, status) =>
(actions[`${identity}_${status}`] || actions['all_default'])()
fn('admin', 1)
fn('admin', 3)
可將巢狀 if 判斷條件整理以 _ 整理成 function 名稱,以 ES6 的 object shorthand 塞進 object,一樣使用 [] 與 || 取代 switch。

Conclusion
- 並不是所有的判斷都只能用
if else與switch case,透過一些 ECMAScript 的語言特性與 higher order function,可以有效降低程式碼複雜度
Reference
Jecelyn Yeen, 5 Tips to Write Better Conditionals in JavaScript
JavaScript 复杂判断的更优雅写法