浅谈if...else...的优化方式

if…else…语句是我们平时写代码时经常用到的,而且使用起来也没啥问题。问题是当业务逻辑比较复杂时,一堆条件判断就看的让人头大了,而且功能迭代时,还有可能在上面进行增量修改,内容越来越长,嵌套越来越深,可读性就变得越来越差。

在JavaScript逻辑运算中,0、’’、null、false、undefined、NaN都会判为false(假),其他都为true(真):

1
2
3
4
5
6
const a = Boolean('');           // false
const b = Boolean(NaN); // false
const c = Boolean(0); // false
const d = Boolean(null); // false
const e = Boolean(undefined); // false
const f = Boolean(false); // false

三元表达式替换if…else…

直接用三元表达式替换简单的if…else…语句。

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (this.tagElecs.showMode === 1) {
if (type === 2) {
await this.$store.dispatch('clientIndustryAnalyzeOne', params)
} else {
await this.$store.dispatch('clientIndustryAnalyzeTwo', params)
}
} else {
if (type === 2) {
await this.$store.dispatch('emphasisClientAnalyzeOne', params)
} else {
await this.$store.dispatch('emphasisClientAnalyzeTwo', params)
}
}

使用三元表达式替换替换内部嵌套的if…else…语句:

1
2
3
4
5
6
7
let dispatchURL = ''
if (this.tagElecs.showMode === 1) {
dispatchURL = type === 2 ? 'clientIndustryAnalyzeOne' : 'clientIndustryAnalyzeTwo'
} else {
dispatchURL = type === 2 ? 'emphasisClientAnalyzeOne' : 'emphasisClientAnalyzeTwo'
}
await this.$store.dispatch(dispatchURL, params)

当if…else…分支逻辑较复杂时,如下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async detailsList () {
// 演示模式
if (this.isDemo) {
this.elecPredSchemeDetailes = demoData
let time = new Date()
const year = time.getFullYear()
const month = time.getMonth() + 1
const day = time.getDate() + 1
time = `${year} - ${month} - ${day}`
this.runDay = time
} else {
this.queryStr = ''
const data = await axiosApi({ url: `/app/load/eems/elecpredschemedetail/dayAhead`, type: 'get' })
this.elecPredSchemeDetailes = data.elecPredSchemeDetailes
this.runDay = formatTime(data.runtime, 'yyyy-MM-dd')
this.count = data.count
}
}

封装if…else…分支逻辑,再使用三元表达式替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 演示模式
onDemoMode () {
this.elecPredSchemeDetailes = demoData
let time = new Date()
const year = time.getFullYear()
const month = time.getMonth() + 1
const day = time.getDate() + 1
time = `${year} - ${month} - ${day}`
this.runDay = time
},

// 正常模式
async onNormalMode () {
this.queryStr = ''
const data = await axiosApi({ url: `/app/load/eems/elecpredschemedetail/dayAhead`, type: 'get' })
this.elecPredSchemeDetailes = data.elecPredSchemeDetailes
this.runDay = formatTime(data.runtime, 'yyyy-MM-dd')
this.count = data.count
},

detailsList () {
this.isDemo ? this.onDemoMode() : this.onNormalMode()
}

&&(与)、||(或)逻辑运算符替换if…else…

1
A && B

当 A 为 true(真)时,执行语句 B,返回语句 B 的结果;当 A 为 false(假)时,不执行语句 B,返回语句 A 的结果。

1
A || B

当 A 为 false(假)时,执行语句 B,返回语句 B 的结果;当 A 为 true(真)时,不执行语句 B,返回语句 A 的结果。

使用 && 运算符替换if语句:

1
2
3
4
5
6
if (id) {
this.$router.push({
path: '/settlementManage/retailSettlement/details',
query: { id, time: this.time }
})
}

等价于:

1
2
3
4
id && this.$router.push({
path: '/settlementManage/retailSettlement/details',
query: { id, time: this.time }
})

使用 || 运算符替换if语句:

1
2
3
if (!activeQuan) {
activeQuan = 0
}

等价于:

1
let quan =  activeQuan || 0

依赖注入消除if…else…

A依赖B,但A不控制B的创建和销毁,仅仅使用B,那么B的控制权交给A之外(的容器)处理,这叫控制反转(IOC),而A要依赖B,必然要使用B的接口,那么:

  • 通过A的接口,把B传入
  • 通过A的构造,把B传入
  • 通过设置A的属性,把B传入

这个过程叫依赖注入(DI)。

如下功能:

1
2
3
4
5
6
7
8
9
function checkStatus (val) {
if (val === 0) {
return '正常'
} else if (val === 1) {
return '未核对'
} else if (val === 2) {
return '核对异常'
} else return '其他'
}

数组元素可以通过索引访问,将依赖(或结果)注入数组可以消除if…else…语句:

1
2
3
4
function checkStatus (val) {
const arr = ['正常', '未核对', '核对异常']
return arr[val] || '其他'
}

另外,对象属性可以通过键名访问,将依赖(或结果)注入对象也可以消除if…else…语句:

1
2
3
4
5
6
7
8
function checkStatus (val) {
const o = {
v_0: '正常',
v_1: '未核对',
v_2: '核对异常'
}
return o[`v_${val}`] || '其他'
}

多态消除if…else…

多态指同一个实体同时具有多种形式。它是面向对象程序设计(OOP)的一个重要特征。简单点说:“一个接口,多种实现”,即同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

如下功能,为了让 renderMap 函数保持一定的弹性, 使用条件分支来让 renderMap 函数同时支持两种地图的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var googleMap = {
show: function () {
console.log('开始渲染谷歌地图');
}
};

var bdMap = {
show: function () {
console.log('开始渲染百度地图');
}
};

var renderMap = function (type) {
if (type === 'google') {
googleMap.show();
} else if (type === 'bd') {
dbMap.show();
}
};

renderMap('google'); // 输出:开始渲染谷歌地图
renderMap( 'baidu' ); // 输出:开始渲染百度地图

可以看到,虽然 renderMap 函数目前保持了一定的弹性,但这种弹性是很脆弱的,一旦需要替换成其他的地图接口,那无疑必须得改动 renderMap 函数,继续往里面堆砌条件分支语句。我们还是先把程序中相同的部分抽象出来,那就是显示某个地图:

1
2
3
4
5
6
7
8
var renderMap = function(map){
if (map.show instanceof Function){
map.show();
}
};

renderMap( googleMap ); // 输出:开始渲染谷歌地图
renderMap( bdMap ); // 输出:开始渲染百度地图

现在来找找这段代码中的多态性。当向两种地图对象分别发出“展示地图”的消息时,会分别调用它们的 show 方法,就会产生各自不同的执行结果。即两种地图有相同的 show 接口,而两种地图 show 接口的表现不一样。对象的多态性表明,“做什么”和“怎么去做”是可以分开的,即使以后增加了其他地图,renderMap 函数仍然不需要做任何改变。

使用多态优化以下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (value.name === 'clientStatus') {
this.$refs.clientStatus.getClientStatus()
} else if (value.name === 'apprStep') {
this.$refs.apprStep.init()
} else if (value.name === 'clientData') {
this.$refs.clientData.getClientList()
} else if (value.name === 'tip') {
this.$refs.tip.init()
} else if (value.name === 'mould') {
this.$refs.mould.getList()
} else if (value.name === 'saleGroup') {
this.$refs.saleGroup.initSaleGroup()
} else if (value.name === 'marketConfig') {
this.$refs.marketConfig.init()
} else if (value.name === 'serviceContent') {
this.$refs.serviceContent.init()
}

将所有ref实例的初始化接口设置为init接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (value.name === 'clientStatus') {
this.$refs.clientStatus.init()
} else if (value.name === 'apprStep') {
this.$refs.apprStep.init()
} else if (value.name === 'clientData') {
this.$refs.clientData.init()
} else if (value.name === 'tip') {
this.$refs.tip.init()
} else if (value.name === 'mould') {
this.$refs.mould.init()
} else if (value.name === 'saleGroup') {
this.$refs.saleGroup.init()
} else if (value.name === 'marketConfig') {
this.$refs.marketConfig.init()
} else if (value.name === 'serviceContent') {
this.$refs.serviceContent.init()
}

使用[]读取对象属性,获取ref实例。最后执行ref实例的初始化——init函数:

1
2
const ref = this.$refs[value.name] // 获取ref实例
ref.init()

策略模式消除if…else…

策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。即定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。主要解决在有多种算法相似的情况下,使用 if…else… 所带来的复杂和难以维护的问题。

如下功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 归档合同
async archiveContract (id) {
try {
await this.$store.dispatch('setContractArchive', { id: id })
this.$message.success('归档合同成功!')
this.form.page = 1
this.getList()
} catch (e) {
console.log(e)
}
},

// 作废合同
async handleCancel (row) {
try {
await this.$store.dispatch('setContractCancel', row.id)
this.$message.success('作废合同成功!')
this.form.page = 1
this.getList()
} catch (e) {
console.log(e)
}
},

// 删除合同
async deleteContract (crtId) {
try {
await this.$store.dispatch('deleteContract2', crtId)
this.$message.success('删除成功!')
this.form.page = 1
this.getList()
} catch (e) {
console.log(e)
}
}

使用if…else…语句简化代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async operateContract (id, type = 'archive') {
try {
if (type === 'archive') {
await this.$store.dispatch('setContractArchive', { id: id })
this.$message.success('归档合同成功!')
} else if (type === 'cancel') {
await this.$store.dispatch('setContractCancel', id)
this.$message.success('作废合同成功!')
} else if (type === 'del') {
await this.$store.dispatch('deleteContract2', id)
this.$message.success('删除成功!')
}
this.form.page = 1
this.getList()
} catch (e) {
console.log(e)
}
}

在JavaScript中函数是一等对象,可以作为参数传递到函数内部执行,该函数称为回调函数。利用高阶函数的这一特性,JavaScript策略模式更简洁。下面将三种合同操作进行函数封装,用作参数传递到operateContract函数内部执行,实现策略模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 归档合同
async archive (id) {
await this.$store.dispatch('setContractArchive', { id: id })
return '归档合同成功!'
},

// 废除合同
async cancel (id) {
await this.$store.dispatch('setContractCancel', id)
return '作废合同成功!'
},

// 删除合同
async del (id) {
await this.$store.dispatch('deleteContract2', id)
return '删除成功!'
},

/**
* 操作合同
* @param {function} fn - 函数
*/
async operateContract (fn) {
try {
const message = await fn()
this.$message.success(message)
this.form.page = 1
this.getList()
} catch (e) {
console.log(e)
}
}

同样的,也可以使用依赖注入,将函数注入到对象ContractOperation中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const ContractOperation = {
archive: archive,
cancel: cancel,
del: del
}

/**
* 操作合同
* @param {number} id - 合同id
* @param {string} [type='archive'] - 操作类型: archive(删除合同)、cancel(废除合同)、delete(删除合同),默认archive
*/
async operateContract (id, type = 'archive') {
try {
const message = await ContractOperation[type].call(this, id)
this.$message.success(message)
this.form.page = 1
this.getList()
} catch (e) {
console.log(e)
}
}

总结

综上所述,if…else…的优化方法如下:

  • 三元表达式替换if…else…
  • &&(与)、||(或)逻辑运算符替换if…else…
  • 依赖注入消除if…else…
  • 多态消除if…else…
  • 策略模式消除if…else…