如何優(yōu)化JavaScript中的復(fù)雜判斷?
我們在寫JavaScript代碼的時(shí)候,經(jīng)常會遇到邏輯判斷比較復(fù)雜的情況,通常我們可以使用if/else或者switch來實(shí)現(xiàn)多個(gè)條件判斷。
但是有一個(gè)問題,隨著邏輯復(fù)雜度的增加,代碼中的if/else/switch會越來越臃腫,難以理解。那么如何才能寫出更優(yōu)雅的判斷邏輯呢?
示例:
/**
* Button click event
* @param {number} status:1 2 3 4 5
*/
const onButtonClick = (status)=>{
if(status == 1){
jumpTo('IndexPage')
}else if(status == 2){
jumpTo('FailPage')
}else if(status == 3){
jumpTo('FailPage')
}else if(status == 4){
jumpTo('SuccessPage')
}else if(status == 5){
jumpTo('CancelPage')
}else {
jumpTo('Index')
}
}
從代碼中我們可以看到這個(gè)按鈕的點(diǎn)擊邏輯:根據(jù)不同的活動(dòng)狀態(tài),做兩件事,發(fā)送日志事件,跳轉(zhuǎn)到對應(yīng)頁面。大家很容易就提出使用 switch 進(jìn)行代碼重寫方案:
const onButtonClick = (status)=>{
switch (status){
case 1:
console.log('IndexPage')
break
case 2:
case 3:
jumpTo('FailPage')
break
case 4:
jumpTo('SuccessPage')
break
case 5:
jumpTo('CancelPage')
break
default:
jumpTo('Index')
break
}
}
這樣看起來比用 if/else 清晰多了,你還發(fā)現(xiàn)了一個(gè)小技巧:當(dāng) case 2 和 case 3 的邏輯相同時(shí),可以省略執(zhí)行語句和 break,這樣 case 2 的邏輯就會自動(dòng)執(zhí)行 case 3 的邏輯。
持續(xù)優(yōu)化:
const actions = {
'1': ['IndexPage'],
'2': ['FailPage'],
'3': ['FailPage'],
'4': ['SuccessPage'],
'5': ['CancelPage'],
'default': ['Index'],
}
const onButtonClick = (status)=>{
let action = actions[status] || actions['default'],
jumpTo(action[0])
}
現(xiàn)在代碼確實(shí)看起來干凈多了,這個(gè)方法的巧妙之處在于:把判定條件作為對象的屬性名,把處理邏輯作為對象的屬性值,點(diǎn)擊按鈕時(shí)通過查找對象屬性進(jìn)行邏輯判斷,這種寫法特別適合一元條件判斷。
還有其他寫法嗎?用map:
const actions = new Map([
[1, ['IndexPage']],
[2, ['FailPage']],
[3, ['FailPage']],
[4, ['SuccessPage']],
[5, ['CancelPage']],
['default', ['Index']]
])
const onButtonClick = (status)=>{
let action = actions.get(status) || actions.get('default')
jumpTo(action[0])
}
這樣寫的話,就用到了ES6中的Map對象,是不是感覺順暢多了?Map對象和Object有什么區(qū)別?
- 對象通常有自己的原型,所以對象總有一個(gè)原型鍵。
- 對象的鍵只能是字符串或者Symbol,而Map的鍵可以是任意值。
Map中鍵值對的數(shù)量可以通過size屬性輕松獲取,而對象中鍵值對的數(shù)量只能手動(dòng)確認(rèn)。
復(fù)雜一點(diǎn)的話,再加一層判斷如何?
const onButtonClick = (status,identity)=>{
if(identity == 'guest'){
if(status == 1){
//do sth
}else if(status == 2){
//do sth
}else if(status == 3){
//do sth
}else if(status == 4){
//do sth
}else if(status == 5){
//do sth
}else {
//do sth
}
}else if(identity == 'master') {
if(status == 1){
//do sth
}else if(status == 2){
//do sth
}else if(status == 3){
//do sth
}else if(status == 4){
//do sth
}else if(status == 5){
//do sth
}else {
//do sth
}
}
}
從上面的例子我們可以看出,當(dāng)你的邏輯升級為二元判斷的時(shí)候,判斷量和代碼量都會翻倍,這時(shí)候怎么才能寫得更干凈呢?
const actions = new Map([
['guest_1', ()=>{/*do sth*/}],
['guest_2', ()=>{/*do sth*/}],
['guest_3', ()=>{/*do sth*/}],
['guest_4', ()=>{/*do sth*/}],
['guest_5', ()=>{/*do sth*/}],
['master_1', ()=>{/*do sth*/}],
['master_2', ()=>{/*do sth*/}],
['master_3', ()=>{/*do sth*/}],
['master_4', ()=>{/*do sth*/}],
['master_5', ()=>{/*do sth*/}],
['default', ()=>{/*do sth*/}],
])
const onButtonClick = (identity,status)=>{
let action = actions.get(`${identity}_${status}`) || actions.get('default')
action.call(this)
}
上述代碼的核心邏輯是:將兩個(gè)條件拼接成一個(gè)字符串,以拼接后的條件字符串為鍵,以處理函數(shù)為值,通過 Map 對象查找并執(zhí)行。這種方法在做多條件判斷的時(shí)候特別有用。
當(dāng)然,使用 Object 實(shí)現(xiàn)上述代碼也是類似的:
const actions = {
'guest_1':()=>{/*do sth*/},
'guest_2':()=>{/*do sth*/},
//....
}
const onButtonClick = (identity,status)=>{
let action = actions[`${identity}_${status}`] || actions['default']
action.call(this)
}
如果把查詢條件拼接成字符串感覺有點(diǎn)別扭的話,還有另外一個(gè)解決辦法,就是使用一個(gè)以 Object 對象為鍵的 Map 對象:
const actions = new Map([
[{identity:'guest',status:1},()=>{/*do sth*/}],
[{identity:'guest',status:2},()=>{/*do sth*/}],
//...
])
const onButtonClick = (identity,status)=>{
let action = [...actions].filter(([key,value])=>(key.identity == identity && key.status == status))
action.forEach(([key,value])=>value.call(this))
}
這里我們也可以看出Map和Object的區(qū)別,Map可以使用任意類型的數(shù)據(jù)作為key。
我們再增加一點(diǎn)難度,假設(shè)一個(gè)客人的情況,狀態(tài)1-4的處理邏輯都一樣,我們該如何處理呢?最壞的情況是這樣的:
const actions = ()=>{
const functionA = ()=>{/*do sth*/}
const functionB = ()=>{/*do sth*/}
return new Map([
[{identity:'guest',status:1},functionA],
[{identity:'guest',status:2},functionA],
[{identity:'guest',status:3},functionA],
[{identity:'guest',status:4},functionA],
[{identity:'guest',status:5},functionB],
//...
])
}
const onButtonClick = (identity,status)=>{
let action = [...actions()].filter(([key,value])=>(key.identity == identity && key.status == status))
action.forEach(([key,value])=>value.call(this))
}
這樣寫已經(jīng)可以滿足日常需求了。
不過說真的,重寫 functionA 四次還是有點(diǎn)麻煩的。如果情況變得特別復(fù)雜,比如,身份有三種狀態(tài),狀態(tài)有十種狀態(tài),那么就需要定義 30 個(gè)處理邏輯。
而且往往這些邏輯很多都是相同的,這看起來有點(diǎn)讓人難以接受。那么可以這樣實(shí)現(xiàn):
const actions = ()=>{
const functionA = ()=>{/*do sth*/}
const functionB = ()=>{/*do sth*/}
return new Map([
[/^guest_[1-4]$/,functionA],
[/^guest_5$/,functionB],
//...
])
}
const onButtonClick = (identity,status)=>{
let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`)))
action.forEach(([key,value])=>value.call(this))
}
這里 Map 的優(yōu)勢就更加凸顯了,它允許使用正則類型作為鍵,這帶來了無限可能。假設(shè)需求發(fā)生了變化,每個(gè)客人場景都需要發(fā)送日志事件,不同的狀態(tài)場景也需要單獨(dú)的邏輯處理。那么,我們可以這樣寫:
const actions = ()=>{
const functionA = ()=>{/*do sth*/}
const functionB = ()=>{/*do sth*/}
const functionC = ()=>{/*send log*/}
return new Map([
[/^guest_[1-4]$/,functionA],
[/^guest_5$/,functionB],
[/^guest_.*$/,functionC],
//...
])
}
const onButtonClick = (identity,status)=>{
let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`)))
action.forEach(([key,value])=>value.call(this))
}
也就是說,利用數(shù)組循環(huán)的特性,滿足正則表達(dá)式條件的邏輯就會被執(zhí)行,這樣就可以同時(shí)執(zhí)行通用邏輯和個(gè)別邏輯。有了正則表達(dá)式的存在,你可以發(fā)揮你的想象力,解鎖更多的可能性。