Appearance
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
2
3
4
5
6
7
使用三元表达式优化:
js
// Good 😄
let temp = condition ? 'foo' : 'bar'
1
2
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
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
4
5
6
7
2. 合并条件表达式
例 3.1
js
// Bad 😢
if (condition1) {
if (condition2) {
doSomething()
}
}
1
2
3
4
5
6
2
3
4
5
6
合并嵌套条件语句:
js
// Good 😄
if (condition1 && condition2) {
doSomething()
}
1
2
3
4
2
3
4
例 3.2
js
// Bad 😢
if (condition1) {
doSomething()
}
if (condition2) {
doSomething()
}
1
2
3
4
5
6
7
2
3
4
5
6
7
合并相同情况条件语句:
js
// Good 😄
if (condition1 || condition2) {
doSomething()
}
1
2
3
4
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
怎么样,代码是不是优雅简洁多了。
7. includes / indexOf
includes
和 indexOf
都是数组方法,前者用于查找某个数组中是否存在某个元素,后者是获取数组中第一个目标元素的下标。
例 7.1
js
// Bad 😢
if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'cherry') {
// 苹果、草莓和樱桃都是红色的
console.log('red')
}
1
2
3
4
5
2
3
4
5
使用 includes
优化:
js
// Good 😄
if (['apple', 'strawberry', 'cherry'].includes(fruit)) {
// 苹果、草莓和樱桃都是红色的
console.log('red')
}
1
2
3
4
5
2
3
4
5
使用 indexOf
优化:
js
// Good 😄
if (['apple', 'strawberry', 'cherry'].indexOf(fruit) !== -1) {
// 苹果、草莓和樱桃都是红色的
console.log('red')
}
1
2
3
4
5
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
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
2
3
4