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 复杂判断的更优雅写法