if/else 的重构方案
背景
日常开发中总会遇到条件判断,而写程序归根结底也是各种条件判断来控制程序执行的。最常用也是最信手拈来的就是 if/else, 或者优雅点会用到 switch 来实现多个条件的判断。当然 if/else 的使用在开发人员当中也时常会引起热烈讨论,到底条件判断怎么写会更优雅效率更高,也在每个程序编写者心中有着各自不同的认识和定义。
接下来做了一些条件判断的使用场景和优化方案的介绍,目的不是批判某种用法,仅是把更多的可使用方案介绍给大家。
介绍
总结前置:
- if...
- if...else...
- if...else if...else
- switch / case
- JavaScript 三元运算
- 短路运算符
- Logic + Control (数据结构 + 算法)方式
首先,明确两个最基本的条件判断,if/else 和 switch/case 的用法。
1. if 应用场景:单个分支执行中插入额外执行 如下:
var n = 1
console.log('n 等于', n )
if ( n < 10 ) {
console.log('n 小于10 ')
}
n++
console.log('n 等于 ', n )
# 输入出结果:
n 等于 1
n 小于10
n 等于 2
# 当n=20时,输出结果:
n 等于 20
n 等于 21
工作原理:
if 只有当指定条件为 Boolean值 为 true 时,该语句才会执行代码。
应用场景:
a. 值匹配
b. 区间判断
2. if/else 应用场景:两个分支的判断 如下:
var n = true
if (n) {
console.log('n 等于 true')
}
else {
console.log('n 不等于 true')
}
3. if/else if/else 应用场景:至少三个分支的判断 如下:
a.值匹配:
var n = 'c'
if (n==='a') {
console.log('---> a')
}
else if (n==='b') {
console.log('---> b')
}
else {
console.log('---> c')
}
b.区间判断
var n = 6
if ( n < 5 ) {
console.log('n 小于 5')
}
else if (n >= 5 && n < 10) {
console.log('n 小于 10,并且不小于 5')
}
else {
console.log('n 不小于 10')
}
应用场景:
a. 值匹配
b. 区间判断
4. switch/case 应用场景:多分支判断 如下:
var n = 'c'
switch (n) {
case 'a':
console.log('---> a')
break
case 'b':
console.log('----> b')
break
default:
console.log('----> default')
}
工作原理:
首先设置表达式 n(通常是一个变量)。随后表达式的值会与结构中的每个 case 的值做比较。如果存在匹配,则与该 case 关联的代码块会被执行。请使用 **break**
来阻止代码自动地向下一个 case 运行。
应用场景:
a. 值匹配
其次, 除了上述常用的两种条件判断方式,还有一个不得不说的判断方式,JavaScript三目运算。
5. JavaScript三元运算的应用场景:两个分支判断的快捷写法 如下:
var n = 5
n > 3 ? alert("5大于3") : alert("5小于3");
工作原理:
(condition1) ? ture-doing : else-doing; 语句在条件为 true 时执行代码,在条件为 false 时执行其他代码。
应用场景:
a. 值匹配
b. 区间判断
可以看出,三目运算可以替代 if...else... 的使用,但与 if..else... 稍有区别,if/else没有返回值,而三元运算是有返回值的,如下:
// if...else..
var n=1;
if(n>1){
n=0;
}else{
++n;
}
console.log(n);
#输出结果:2
// 三目运算
var n=1;
n = n>1?0 : ++n;
console.log(n);
#输出结果为:2
6. 短路运算符 如下:
var a = true
var b = true
var c = function() {
console.log('c函数执行了!')
return false
}
var d = function() {
console.log('d函数执行了!')
return true
}
var e = function() {
console.log('e函数执行了!')
return true
}
a && b && c() && d() || e()
#输出结果为:
c函数执行了!
e函数执行了!
应用场景:
Boolean 值 的连续判断,“&&” 只要不遇到 false 就会一直往下执行,遇到 false 会去执行 “||”后边的执行语句
值得注意的是:
1. 如果执行的是个函数,没有返回值,那么默认是 return false
2. 判断值是 0 和 1 时,0相当于false,1相当于true
7. Logic + Control (数据结构+算法),如下:
const sendLog = function(LogName) {
console.log( 'log result ----> ', LogName )
}
const jumpTo = function (pageName) {
console.log( '要去到的页面 ----> ', pageName )
}
const actions = {
'1': ['processing', 'IndexPage'],
'2': ['fail', 'FailPage'],
'3': ['fail', 'FailPage'],
'4': ['success', 'SuccessPage'],
'5': ['cancel', 'CancelPage'],
'default': ['other', 'Index']
}
const clickHandler = (status) => {
let action = actions[status] || actions['default'],
LogName = action[0],
pageName = action[1]
sendLog(LogName)
jumpTo(pageName)
}
应用场景:
判断条件(即:状态值)有统一明确的匹配规则,如:可枚举型,可 switch/case 判断 等有明确的状态值的场景。
先举个一个多层 if/else 嵌套的代码重构的例子,让大家理解其使用场景(代码取于网上 稍作修改):
案例:
const sendLog = function(LogName) {
console.log( 'log result ----> ', LogName )
}
const jumpTo = function (pageName) {
console.log( '要去到的页面 ----> ', pageName )
}
const clickHandler = (status) => {
if(status === 1) {
sendLog('processing')
jumpTo('IndexPage')
} else if(status === 2) {
sendLog('fail')
jumpTo('FailPage')
} else if(status === 3) {
sendLog('fail')
jumpTo('FailPage')
} else if(status === 4) {
sendLog('success')
jumpTo('SuccessPage')
} else if(status === 5) {
sendLog('cancel')
jumpTo('CancelPage')
} else {
sendLog('other')
jumpTo('Index')
}
}
优化1:
swich/case 方式重构:
const clickHandler = (status) => {
switch (status) {
case 1:
sendLog('processing')
jumpTo('IndexPage')
break
case 2:
case 3:
sendLog('fail')
jumpTo('FailPage')
break
case 4:
sendLog('success')
jumpTo('SuccessPage')
break
case 5:
sendLog('cancel')
jumpTo('CancelPage')
break
default:
sendLog('other')
jumpTo('Index')
}
}
注意: case 2
与 case 3
的逻辑一样,可以省略写法。
代码优雅了些,并清洗了许多。
优化2:
logic
+ control
方式 重构:
const actions = {
'1': ['processing', 'IndexPage'],
'2': ['fail', 'FailPage'],
'3': ['fail', 'FailPage'],
'4': ['success', 'SuccessPage'],
'5': ['cancel', 'CancelPage'],
'default': ['other', 'Index']
}
const clickHandler = (status) => {
let action = actions[status] || actions['default'],
LogName = action[0],
pageName = action[1]
sendLog(LogName)
jumpTo(pageName)
}
代码清爽了许多,并去除了重复代码的编写。
优化3:
logic
+ control
方式,加Map
对象的应用:
const actions = new Map([
['1', ['processing', 'IndexPage']],
['2', ['fail', 'FailPage']],
['3', ['fail', 'FailPage']],
['4', ['success', 'SuccessPage']],
['5', ['cancel', 'CancelPage']],
['default', ['other', 'Index']]
])
const clickHandler = (status) => {
let action = actions.get(status) || actions.get('default')
sendLog(action[0])
jumpTo(action[1])
}
Map
对象的优势,是 key 值可以是任意值(Boolean, String, Object, 正则 等)。
理解与启发:一个多层 if/else 嵌套 重构的例子
一个两层的if/else嵌套,根据用户身份 和 status 做不同的处理,理解整个重构的过程,你会获益匪浅:
例子:
const clickHandler = (status, identity) => {
if(identity == 'guest') {
if(status === 1) {
// to do something
} else if (status === 2) {
// to do something
} else if (status === 3) {
// to do something
} else if (status === 4) {
// to do something
} else if (status === 5) {
// to do something
} else {
// to do something
}
} else if(identity == 'master') {
if(status === 1) {
// to do something
} else if (status === 2) {
// to do something
} else if (status === 3) {
// to do something
} else if (status === 4) {
// to do something
} else if (status === 5) {
// to do something
} else {
// to do something
}
}
}
缺点:代码冗长,逻辑梳理起来较为复杂,后续维护困难:
优化 1:
const actions = new Map([
['guest_1', () => {/* to do something */}],
['guest_2', () => {/* to do something */}],
['guest_3', () => {/* to do something */}],
['guest_4', () => {/* to do something */}],
['guest_5', () => {/* to do something */}],
['master_1', () => {/* to do something */}],
['master_2', () => {/* to do something */}],
['master_3', () => {/* to do something */}],
['master_4', () => {/* to do something */}],
['master_5', () => {/* to do something */}],
['default', () => {/* to do something */}],
])
上述代码的逻辑:
- 把两个条件拼接成字符串
- 以拼接的条件字符串作为
key
,以处理函数作为值的Map
对象进行查找并执行
优化 2:
也可以用对象的方式来实现,这也是我们常用的方式:
const actions = {
'guest_1': () => {/* to do something */},
'guest_2': () => {/* to do something */},
'guest_3': () => {/* to do something */},
'guest_4': () => {/* to do something */},
'guest_5': () => {/* to do something */},
'master_1': () => {/* to do something */},
'master_2': () => {/* to do something */},
'master_3': () => {/* to do something */},
'master_4': () => {/* to do something */},
'master_5': () => {/* to do something */},
'default': () => {/* to do something */}
}
优化 3:
当然觉得拼接字符串的方式不够优雅,也可以用对象作为key
, 如下:
const actions = new Map([
[{indentity: 'guest', status: 1}, () => {/* to do something */}],
[{indentity: 'guest', status: 2}, () => {/* to do something */}],
[{indentity: 'guest', status: 3}, () => {/* to do something */}],
[{indentity: 'guest', status: 4}, () => {/* to do something */}],
[{indentity: 'guest', status: 5}, () => {/* to do something */}],
[{indentity: 'guest', status: 'default'}, () => {/* to do something */}],
[{indentity: 'master', status: 1}, () => {/* to do something */}],
[{indentity: 'master', status: 2}, () => {/* to do something */}],
[{indentity: 'master', status: 3}, () => {/* to do something */}],
[{indentity: 'master', status: 4}, () => {/* to do something */}],
[{indentity: 'master', status: 5}, () => {/* to do something */}],
[{indentity: 'master', status: 'default'}, () => {/* to do something */}],
])
const clickHandler = (identity, status) => {
let action = [...actions].filter((key, value) => {
key.identity === identity && key.status === status
})
action.forEach(([key, value]) => {
value.call(this)
})
}
Map
与Object
的区别在于,Map
的key
值可以是任意数据类型, 这也正是Map
对象的优势。
优化 4:
Map
加 正则表达式匹配的方式:
function A () {
// to do something
}
function B () {
// to do something
}
function C () {
// to do something
}
const actions = new Map([
[/^guest_[1-4]$/, fucnctionA],
[/^master_[1-5]$/, functionB],
[/^guest_.$/, functionC]
])
const clickHandler = (identity, status) {
let action = [...actions].filter((key, value) => {key.test(`${identity}_${status}`)})
action.forEach(([key, value]) => {value.call(this)})
}
个人总结:
几种条件判断的最佳使用场景:
-
单分支插入执行块
// a. if 方式 if (【条件】) { doSomething...} // b.短路运算符方式 【条件】 && (doSomething...)
-
两个分支的执行
// a. if...else... 方式 if (【条件】) { // do something ... } else { // do something ... } // b. 三元运算符 【条件】 ? (doSomethingA...) : (doSomethingB...) // c. 短路运算符方式 【条件】 && (doSomethingA...) || (doSomethingB)
-
三个分支的执行
// a. if...else if...else... 方式 if (【条件1】) { // do something ... } else if (【条件2】) { // do something ... } else { // do something ... } // b. 三元运算符 (勉强接受) 【条件1】 ? (doSomethingA...) : (【条件2 ? (doSomethingB...) : (doSomethingC...)】) // c.短路运算符 方式(不推荐) 【条件1】&& (doSomethingA...) || (【条件2】&& (doSomethingB..., true)) || (doSomethingC...)
-
多个分支的执行
// a. if...else if...else... (不推荐) // b. switch...case...(确定的枚举个数,并且数量不小于3个,推荐使用,并且 可枚举个数越多越不推荐) switch (status) { case 【status值1】: // do something ... break case 【status值2】: // do something ... break case 【status值3】: // do something ... break default: // do something ... break } // c. logic + control(推荐) const actions = new Map([ [【key1】, 【value1】], [【key2】, 【value2】], [【key3】, 【value3】], [【key4】, 【value4】], ... ]) const handler = function(【条件】) { let action = [...actions].filter((key, value) => { key 匹配 【条件】 }) action.forEach(([key, value]) => {value.call(this)}) }
【注】:如果判断的条件是区间值,可先将区间进行分类划分后再使用 b 或 c 的判断方式