所有文章

使用 Map 作條件判斷

JavaScript 中遇到複雜邏輯判斷時,通常用 switch 代替 if/else。隨著邏輯複雜度增加,這兩種條件判斷方式都會變得臃腫、難明。本文旨在介紹一些優雅的條件判斷方式。

使用 Object

將判斷條件作為對象的鍵,處理行為作為相應值,再透過查找對象屬性的方式來作條件判斷——這種寫法尤適合一元條件判斷之情況,亦為眾所知:

const actions = {
  '1': ['processing', 'IndexPage'],
  '2': ['fail', 'FailPage'],
  '3': ['fail', 'FailPage'],
  '4': ['success', 'SuccessPage'],
  '5': ['cancel', 'CancelPage'],
  'default': ['other', 'IndexPage'],
}
/**
 * 按鈕點擊事件
 * @param {number} status 活動狀態:1 開團進行中,2 開團失敗,3 商品售罄,4 開團成功,5 系統取消
 */
const onButtonClick = (status) => {
  let action = actions[status] || actions['default'],
      logName = action[0],
      pageName = action[1]
  sendLog(logName)
  jumpTo(pageName)
}

使用 Map

ES2015 推出的 Map 對象,與 Object 對象之區別在於:

  • Object 的鍵祇能是字符串或 Symbol,但 Map 的鍵可以是任意值
  • 可透過 size 屬性輕易地得到一 Map 的鍵值對數

基本

const actions = new Map([
  [1, ['processing', 'IndexPage']],
  [2, ['fail', 'FailPage']],
  [3, ['fail', 'FailPage']],
  [4, ['success', 'SuccessPage']],
  [5, ['cancel', 'CancelPage']],
  ['default', ['other', 'Index']]
])
/**
 * 按鈕點擊事件
 * @param {number} status 活動狀態:1 開團進行中,2 開團失敗,3 商品售罄,4 開團成功,5 系統取消
 */
const onButtonClick = (status) => {
  let action = actions.get(status) || actions.get('default')
  sendLog(action[0])
  jumpTo(action[1])
}

多元條件判斷

現在把問題升級一下,在判斷 status 的同時,還需要判斷用戶的身份。若用 if/elseswitch 來寫,會非常囉嗦冗長;但用 Map 就能寫得清爽:

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*/}],
])

/**
 * 按鈕點擊事件
 * @param {string} identity 身份標識:guest客態 master主態
 * @param {number} status 活動狀態:1 開團進行中,2 開團失敗,3 開團成功,4 商品售罄,5 有庫存未開團
 */
const onButtonClick = (identity, status) => {
  let action = actions.get(`${identity}_${status}`) || actions.get('default')
  action.call(this)
}

上例代碼用把兩個條件拼接成字符串為鍵、以處理函數為值的 Map 進行查找和執行,這種寫法尤適合多元條件判斷。當然這種方式用 Object 也是可行的。若嫌把查詢條件拼成字符串略顯彆扭,則還可用 Object 作鍵:

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))
}

合併查詢結果

再對需求作升級:identity 為 guest 的情況下,status 為 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))
}

上例重寫了 4 次 functionA,而且假如判斷條件愈加複雜,需要定義的處理邏輯亦將增多。此時 Map 就顯出優勢了:可以使用正則類型作鍵。改寫如下:

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],  // 甚至可作通配,實現凡是 guest 都要發送一日誌埋點
    // ...
  ])
}

const onButtonClick = (identity, status)=>{
  let action = [...actions()].filter(([key, value]) => (key.test(`${identity}_${status}`)))
  action.forEach(([key, value])=>value.call(this))
}

完 :)

發佈於 7 Nov 2018

慎獨|純亦不已
Jason Lam on Twitter