Skip to content

27. if-else 代码优化方案

高级语言的代码是给人看的,写出优雅的代码不仅让我们自己赏心悦目,也让同行对我们刮目相看。未来我们自己或者其他人来维护项目的时候也能方便许多,避免“开发一时爽,维护火葬场”。

优雅,永不过时。这里给出 7 个优化 if-else/switch 的方案。

1. 三元表达式

使用三元表达式可以代替简单的 if-else 语句,不过不建议写嵌套三元表达式,最好不要超过两层。某些特殊情况可能需要更多层,比如 TS 类型体操的条件类型的时候。

例 1.1

js
// Bad 😢
let temp = ''
if (condition) {
  temp = 'foo'
} else {
  temp = 'bar'
}
1
2
3
4
5
6
7

使用三元表达式优化:

js
// Good 😄
let temp = condition ? 'foo' : 'bar'
1
2

例 1.2

js
// Bad 😢
if (count < 10) {
  if (weight < 5) {
    price = 30
  } else {
    price = 35
  }
} else {
  if (weight < 10) {
    price = 20
  } else {
    price = 25
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

少数情况下要写嵌套三元表达式,例如 vue 的插值语法 里或者 v-bind 绑定的变量里(实际上可以写 computed 等优化),又或者是 TS 类型体操的条件类型的时候必须写,那推荐下面这种格式的写法,可读性不比 if-else 差(bushi):

js
// Good 😄
// 阶梯型体现出判断层级
price = (
  count < 10
    ? weight < 5 ? 30 : 35
    : weight < 10 ? 20 : 25
)
1
2
3
4
5
6
7

2. 合并条件表达式

例 3.1

js
// Bad 😢
if (condition1) {
  if (condition2) {
    doSomething()
  }
}
1
2
3
4
5
6

合并嵌套条件语句:

js
// Good 😄
if (condition1 && condition2) {
    doSomething()
}
1
2
3
4

例 3.2

js
// Bad 😢
if (condition1) {
  doSomething()
}
if (condition2) {
  doSomething()
}
1
2
3
4
5
6
7

合并相同情况条件语句:

js
// Good 😄
if (condition1 || condition2) {
    doSomething()
}
1
2
3
4

3. 提前 return,减少嵌套

在函数内部,可以使用 return 终止函数继续执行,所以可以使用 return 来减少 else,从而简化代码。

例 3.1

js
// Bad 😢
function price(count) {
  let price = 0
  if (count < 10) {
    price = 15
  } else if (count < 20) {
    price = 18
  } else {
    price = 20
  }
  return res
}
1
2
3
4
5
6
7
8
9
10
11
12
js
// Good 😄
function price(count) {
  if (count < 10) return 15
  if (count < 20) return 18
  return 20
}
1
2
3
4
5
6

例 3.2 求三数中的最大值

js
// Bad 😢
function max(x, y, z) {
  let res = -Infinity
  if (x > y) {
    if (x > z) res = x
    else res = z
  } else {
    if (y > z) res = y
    else res = z
  }
  return res
}
1
2
3
4
5
6
7
8
9
10
11
12

通过提前 return 和合并条件表达式,代码变得简洁许多:

js
// Good 😄
function max(x, y, z) {
  if (x > y && x > z) return x
  if (y > x && y > z) return y
  return z
}
1
2
3
4
5
6

4. 主干代码置后,走正常流程

提前判断不满足条件的情况,然后 return 或者 throw 退出,主干代码(正常情况)放到后面,走正常流程。

例 4.1

js
// Bad 😢
function getApples(total, count) {
  if (total > 0) {
    if (count > 0) {
      if (total >= count) {
        console.log('成功拿到苹果')
        total -= count
      } else {
        console.log('苹果不足')
      }
    } else {
      console.log('不能取 0 个')
    }
  } else {
    console.log('没有苹果了')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

先让异常情况退出,将主干代码置后,程序逻辑清晰许多。

js
// Good 😄
function getApples(total, count) {
  // 先判断异常情况
  if (total <= 0) {
    return console.log('没有苹果了')
  }
  if (count <= 0) {
    return console.log('不能取 0 个')
  }
  if (total < count) {
    return console.log('苹果不足')
  }
  // 主干代码
  total -= count
  console.log('成功拿到苹果')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

5. 表驱动

表驱动(Table-Driven Methods)是在一个信息表中根据条件查找目标对象,而不是使用 if-else/switch 等逻辑语句来查找。这个表是一个 从条件输入到结果输出的映射关系

js
condition -> table -> result
1
  • condition:条件输入
  • table:映射表
  • result:结果输出

WARNING

表驱动适用于 switch 语句和有限个条件的离散型 if-else 语句的优化。所谓离散型 if-else 就是指其情况都能列出来,但有些连续型的其实能够转换为离散型,具体见下一节。

表驱动的关键就是建立这个 table,在 JS 中,使用 plain object 或者 Map 类型都能创建一个映射表。

例 5.1

js
// Bad 😢
const rainbowColor = n => {
  if (n === 1) return 'red'
  if (n === 2) return 'orange'
  if (n === 3) return 'yellow'
  if (n === 4) return 'green'
  if (n === 5) return 'blue'
  if (n === 6) return 'indigo'
  if (n === 7) return 'violet'
}
1
2
3
4
5
6
7
8
9
10

看到这么多 if 确实有点麻,写了很多重复的代码,先使用 switch-case 优化:

js
// Bad 😢
const rainbowColor = n => {
  switch (n) {
    case 1:
      return 'red'
    case 2:
      return 'orange'
    case 3:
      return 'yellow'
    case 4:
      return 'green'
    case 5:
      return 'blue'
    case 6:
      return 'indigo'
    case 7:
      return 'violet'
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

并没有解决问题,冗余代码更多了,使用表驱动方法优化如下,这下赏心悦目多了!

js
// Good 😄
const rainbowColor = n => {
  const obj = {
    1: 'red',
    2: 'orange',
    3: 'yellow',
    4: 'green',
    5: 'blue',
    6: 'indigo',
    7: 'violet'
  }
  return obj?.[n] ?? 'not found'
}
1
2
3
4
5
6
7
8
9
10
11
12
13

或者你可以使用 Map 建立“表”:

js
// Good 😄
const rainbowColor = n => {
  const map = new Map()
    .set(1, 'red')
    .set(2, 'orange')
    .set(3, 'yellow')
    .set(4, 'green')
    .set(5, 'blue')
    .set(6, 'indigo')
    .set(7, 'violet')
  return map?.get(n) ?? 'not found'
}
1
2
3
4
5
6
7
8
9
10
11
12

例 5.2 更多时候,可能都是下面这种情况

js
// Bad 😢
if (condition1) {
  doSomething(param1)
} else if (condition2) {
  doSomething(param2)
} else if (condition3) {
  doSomething(param3)
} else {
  doSomething(param4)
}
1
2
3
4
5
6
7
8
9
10

使用 switch 改造如下,似乎好了一点,代码还是冗余。

js
switch (condition) {
  case condition1:
    return void doSomething(param1)
  case condition2:
    return void doSomething(param2)
  case condition3:
    return void doSomething(param3)
  default:
    return void doSomething(param4)
}
1
2
3
4
5
6
7
8
9
10

注意

return void 保证无返回值,或者说返回 undefined,防止造成某些不可预期的影响。

表驱动改造:

js
// Good 😄
const run = condition => {
  const map = new Map()
    .set(condition1, param1)
    .set(condition2, param2)
    .set(condition3, param3)
  return void doSomething(map?.get(condition) ?? param4)
}

run(condition)
1
2
3
4
5
6
7
8
9
10

6. 离散化

通过之前的讲解,表驱动适合用来优化 switch 语句和情况较少和有限的 if-else 条件语句,对于下面这种情况比较多或者无限情况的(连续型),需要先将其离散化。

例 6.1 成绩评级(保证输入数据在 0-100)

js
// Bad 😢
function scoreLevel(score) {
  if (score >= 90) {
    return 'A'
  } else if (score >= 80) {
    return 'B'
  } else if (score >= 70) {s
    return 'C'
  } else if (score >= 60) {
    return 'D'
  } else {
    return 'D-'
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

对于情况比较多(甚至无限多)的情况,直接使用表驱动有点复杂甚至实现不了。这里需要将连续型的数据转换为离散型的数据,即 将 score/10 然后向下取整,这样每个情况的范围都缩小了,比如 [60, 75) 的计算结果只有 6

js
// Good 😄
function scoreLevel(score) {
  const h = Math.floor(score / 10)
  const map = new Map()
    .set(10, 'A')
    .set(9, 'A')
    .set(8, 'B')
    .set(7, 'C')
    .set(6, 'D')
  return map?.get(h) ?? 'D-'
}
1
2
3
4
5
6
7
8
9
10
11

怎么样,代码是不是优雅简洁多了。

7. includes / indexOf

includesindexOf 都是数组方法,前者用于查找某个数组中是否存在某个元素,后者是获取数组中第一个目标元素的下标。

例 7.1

js
// Bad 😢
if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'cherry') {
  // 苹果、草莓和樱桃都是红色的
  console.log('red')
}
1
2
3
4
5

使用 includes 优化:

js
// Good 😄
if (['apple', 'strawberry', 'cherry'].includes(fruit)) {
  // 苹果、草莓和樱桃都是红色的
  console.log('red')
}
1
2
3
4
5

使用 indexOf 优化:

js
// Good 😄
if (['apple', 'strawberry', 'cherry'].indexOf(fruit) !== -1) {
  // 苹果、草莓和樱桃都是红色的
  console.log('red')
}
1
2
3
4
5

8. 使用函数默认参数和 ??

优化前:

js
// Bad 😢
function getStatusMessage(status, errorCode) {
  if (!status) return 'NA'
  if (errorCode) {
    return `Status:${status}, err:{errorCode}`
  } else {
    return `Status:${status}, err: -1`
  }
}
1
2
3
4
5
6
7
8
9

优化之后:

js
// Good 😄
function getStatusMessage(status = 'NA', errorCode) {
  return `Status:${status}, err:${errorCode ?? -1}`
}
1
2
3
4