本文深入探讨JavaScript中复杂条件判断的优化方案,对比if/else、switch、对象映射及ES6 Map的使用场景。通过从一元到多元、再到正则匹配的案例演进,展示如何利用Map和函数缓存提升代码可读性与执行效率,解决嵌套逻辑臃肿问题。
复杂判断的挑战与优化思路
在JavaScript开发中,处理复杂逻辑判断是常见需求。通常开发者会使用if/else或switch语句来实现多条件分支,但随着业务逻辑复杂度的提升,这类代码往往变得冗长且难以维护。本文将介绍几种更优雅的判断逻辑实现方式,帮助提升代码质量。
接下来通过具体示例进行说明。
首先观察以下基础代码片段。
/**
* 按钮点击事件
* @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
*/
const onButtonClick1 = (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')
}
}
基础方案:Switch与对象映射
上述代码展示了按钮点击事件的逻辑:根据活动状态执行日志埋点发送及页面跳转操作。面对这种多分支场景,常见的改写思路是使用switch语句。
/**
* 按钮点击事件
* @param {number} status 活动状态:1 开团进行中 2 开团失败 3 商品售罄 4 开团成功 5 系统取消
*/
const onButtonClick = (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')
break
}
}
使用switch后,代码结构相比if/else更为清晰。此外,这里展示了一个技巧:当case 2和case 3的处理逻辑相同时,可以省略case 2中的执行语句和break关键字,使其直接落入case 3的逻辑中执行。
部分开发者可能会提出更简洁的替代方案。
const actions = {
'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[status] || actions['default'],
logName = action[0],
pageName = action[1]
sendLog(logName)
jumpTo(pageName)
}
这种写法通过对象属性查找来实现逻辑分发,将判断条件作为对象的键名,处理逻辑作为值。在按钮点击时,通过属性访问获取对应的处理函数。这种方法在一元条件判断场景下尤为适用,代码看起来更加清爽。
是否存在其他更高级的写法?答案是肯定的。
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])
}
进阶方案:ES6 Map对象的应用
此处引入了ES6中的Map对象,相比传统对象,它提供了更强大的功能。那么Map与Object的主要区别在哪里?
首先,普通对象通常包含原型链上的prototype属性,而Map没有。其次,对象的键必须是字符串或Symbol类型,而Map的键可以是任意数据类型。
此外,Map提供了size属性,可以方便地获取键值对的数量;而获取普通对象的键值对数量则需要手动计算或借助Object.keys()等方法。
多元条件判断:二元与三元逻辑处理
现在我们将场景升级:按钮点击事件不仅需要判断status,还需要结合用户身份(identity)进行综合判断。
/**
* 按钮点击事件
* @param {number} status 活动状态:1开团进行中 2开团失败 3 开团成功 4 商品售罄 5 有库存未开团
* @param {string} identity 身份标识:guest客态 master主态
*/
const onButtonClick = (status,identity)=>{
if(identity == 'guest'){
if(status == 1){
//do sth
}else if(status == 2){
//do sth
}else if(status == 3){
//do sth
}else if(status == 4){
//do sth
}else if(status == 5){
//do sth
}else {
//do sth
}
}else if(identity == 'master') {
if(status == 1){
//do sth
}else if(status == 2){
//do sth
}else if(status == 3){
//do sth
}else if(status == 4){
//do sth
}else if(status == 5){
//do sth
}else {
//do sth
}
}
}
此处省略具体的业务逻辑实现,因为代码量较大。
这里再次使用了if/else结构,因为许多开发者在处理此类复杂嵌套逻辑时仍习惯使用这种方式。
从上述例子可以看出,当判断条件升级为二元判断时,逻辑分支数量会成倍增加,代码量也随之激增。此时,如何保持代码的清爽性?
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对象来实现,原理类似。
const actions = {
'guest_1':()=>{/*do sth*/},
'guest_2':()=>{/*do sth*/},
//....
}
const onButtonClick = (identity,status)=>{
let action = actions[`${identity}_${status}`] || actions['default']
action.call(this)
}
如果开发者觉得拼接查询条件字符串的方式不够直观,还有另一种方案:使用Map对象,并以Object对象作为Map的Key。
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))
}
这种写法在语义上是否更加高级?
这进一步体现了Map与Object的区别:Map允许使用任何类型的数据作为Key,包括对象。
高阶技巧:正则匹配与逻辑复用
进一步增加难度:假设在guest身份下,status 1至4的处理逻辑完全相同,最直观但低效的写法如下。
const actions = 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 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,这并不理想。假如判断条件更加复杂,例如identity有3种状态,status有10种状态,则需要定义30条处理逻辑。由于大量逻辑可能相同,这种重复令人难以接受。此时可以优化为:
const actions = ()=>{
const functionA = ()=>{/*do sth*/}
const functionB = ()=>{/*do sth*/}
return new Map([
[/^guest_[1-4]$/,functionA],
[/^guest_5$/,functionB],
//...
])
}
const onButtonClick = (identity,status)=>{
let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`)))
action.forEach(([key,value])=>value.call(this))
}
此处Map的优势更加凸显,因为可以使用正则表达式作为Key,从而带来无限可能性。例如,需求变为:所有guest身份均需发送日志埋点,而不同status需执行独立逻辑,实现方式如下:
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],
//...
])
}
const onButtonClick = (identity,status)=>{
let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`)))
action.forEach(([key,value])=>value.call(this))
}
利用数组循环特性,符合正则条件的逻辑将被依次执行。这样既能执行公共逻辑,又能处理特定逻辑。借助正则表达式的灵活性,可以解锁更多高级玩法,本文不再赘述。
总结
以上介绍了JavaScript中复杂判断的多种优雅写法,希望对读者有所帮助。如有任何疑问,欢迎留言讨论,小编会及时回复。感谢大家对本站的支持!