阅读 5509

[浅析] 特定场景下代替优化 if-else 的方案 (二)

有一再有二,有二再有三。把未知的变成已知,就成了自己的知识

前言

很久之前,发了一篇文章:[浅析]特定场景下取代if-else和switch的方案,但是关于使用 if-else 的场景可不会仅仅是上面文章那么少,还有很多的场景,今天再次写下在开发上有哪些可以代替或者优化 if-else 的场景。

这里强调代替或者优化 if-else,是在特定场景下进行的。目的就是为了在特定场景下改善代码,让代码简洁。增加代码的可读性,维护性,复用性。如果 if-else 使用的场景比较简单,或者代替,优化 if-else 后会对代码产生不好的影响。就不建议使用别的方案代替或者优化,不能为了不写 if-else 而不写,不能为了优化而优化。

1.范围查询

比如抽取中奖的号码区间,中奖的号码区间分别是 9-12,14-18,然后需要判断号码是否中奖了,逻辑很简单就实现了

let num1= 15
let num2= 13
if((num1>=9 && num1<=12)||(num1>=14 && num1<=18)){
     // 中奖了
}
if((num2>=9 && num2<=12)||(num2>=14 && num2<=18)){
     // 中奖了
}
复制代码

这样写貌似没什么问题,如果以后需求变了,中奖的号码不是9-12或者14-18。而是 10-14 或者 18-20。这样就要改原来的代码逻辑

if((num1>=10 && num1<=14)||(num1>=18 && num1<=20)){
     // 中奖了
}
复制代码

或者又有其他需求,比如中奖号码是 9-12,14-18,20-22或者26-30。还是要改动一下 if-else 的逻辑

if((num1>=9 && num1<=12)||(num1>=14 && num<=18)||(num1>=20 && num1<=22)||(num2>=26 && num1<=30)){
     // 数字在范围内
}
复制代码

现在可以用some 进行封装一个函数,只需要一次封装,往后的需求如果范围区间改变了,就可以

function handleCheckRange(num, ...ranges){
    return ranges.some(item=>num>=item[0]&&num<=item[1])
}
handleCheckRange(num1,[9,12],[14,18]) //true
handleCheckRange(num2,[9,12],[14,18]) //false
handleCheckRange(num2,[10,14],[18,20]) //true
handleCheckRange(num2,[9,12],[14,18],[20,22],[26,30]) //false
复制代码

1.可能有人会想到如果再有需求,判断条件不是 >= 或者 <= 。可能是 < 和 >,<= 和 >,或者 < 和 >=呢?这样 handleCheckRange 也是需要改了。这种情况确实,遇到其他的判断条件是需要改一下这个方法。但是改一下这个方法并不难,直接封装就好

2.如果判断条件太多变化,一定要封装方法兼容其他的判断条件,可以参考我的笔记:练习题5:模拟实现开闭区间,这里就不展开讲了。

2.多个与条件

相信大家都会遇到可能会有多个条件组合的问题

比如有一个参与热卖活动赠积分活动,活动状态(status),预热中(status=1),进行中(status=2)。用户类型(type)也有分普通用户(type=1)vip用户(type=2

规则是: 1.在预热中参与活动,vip用户赠送 1000 积分,普通用户赠送 700 积分。 1.在进行中参与活动,vip用户赠送 800 积分,普通用户赠送 300 积分。

之前写法是

let status=1
let type=2
if(status===1){
     if(type===1){
           console.log('普通用户在预售中参与活动,赠送700积分')
    }
    else if(type===2){
           console.log('vip用户在预售中参与活动,赠送1000积分')
    }
}
else if(status===2){
     if(type===1){
           console.log('普通用户在进行中参与活动,赠送300积分')
    }
    else if(type===2){
           console.log('vip用户在进行中参与活动,赠送800积分')
    }
}

// 或者

if(status===1&&type===1){
    console.log('普通用户在预售中参与活动,赠送700积分')
}
else if(status===1&&type===2){
    console.log('vip用户在预售中参与活动,赠送1000积分')
}
else if(status===2&&type===1){
    console.log('普通用户在进行中参与活动,赠送300积分')
}
else if(status===2&&type===2){
    console.log('vip用户在进行中参与活动,赠送800积分')
}
复制代码

但是现在可以使用 obj写法,这样下来,如果以后有什么条件改了,直接改 obj 这个配置就好。

let obj={
   'status=1&type=1':'普通用户在预售中参与活动,赠送700积分',
   'status=1&type=2':'vip用户在预售中参与活动,赠送1000积分',
   'status=2&type=1':'普通用户在进行中参与活动,赠送300积分',
   'status=2&type=2':'普通用户在进行中参与活动,赠送800积分'
}

console.log(obj[`status=${status}&type=${type}`])
复制代码

3.多个或条件

比如输入一个城市名字,输出一个省份名称

let city='广州'
if(city==='广州'||city==='佛山'){
    console.log('广东')
}
else if(city==='海口'||city==='三亚'){
    console.log('海南')
}
复制代码

这样写的弊端就是,如果 if-else 数量一多,代码就会多,而且判断的条件会变得很长,还有一个问题就是风格有可能不会统一

下面用其他方法进行优化下 方法一

let arr=[
    {
        city:'广州',
        province:'广东'
    },
    {
        city:'佛山',
        province:'广东'
    },
    {
        city:'海口',
        province:'海南'
    },
    {
        city:'三亚',
        province:'海南'
    }
]
console.log(arr.filter(item=>item.city===city)[0].province)//广东
复制代码

方法二

let city='广州'
let obj={
    '广州':'广东',
    '佛山':'广东',
    '海口':'海南',
    '三亚':'海南',
}
console.log(obj[city])// 广东
复制代码

见到 广东 和 海南 写了这么多次,可能大家都会郁闷,一个省城市有 20 个,就要写 20 次,可能就有人会想到把城市都作为 key,然后判断传进来的 city 是否在 key 里面,代码如下。

let city='广州'
let obj={
    '广州,佛山':'广东',
    '海口,三亚':'海南',
}
for(let key in obj){
    if(key.includes(city)){
        console.log(obj[key])
        break
    }
}
//广东

复制代码

但是弊端也出来了,就是上面的方法有 bug,只输出城市名称的其中一个字,也能输出对应的省份。

let city='州'
let obj={
    '广州,佛山':'广东',
    '海口,三亚':'海南',
}
for(let key in obj){
    if(key.includes(city)){
        console.log(obj[key])
        break
    }
}
//广东
复制代码

要解决这个问题,两个方式,一种是把 key 转成数组再判断,或者使用 Map

// 方式一:把 key 转成数组再判断

let city='广州'
let obj={
    '广州,佛山':'广东',
    '海口,三亚':'海南',
}
let keys=[]
for(let key in obj){
    keys=key.split(',')
    if(keys.includes(city)){
        console.log(obj[key])
        break
    }
}


//  方式二:使用Map
let city='广州'
let map=new Map([
     [['广州','佛山'],'广东'],
     [['海口','三亚'],'海南'],
])
for (let key of map.keys()) {
  if(key.includes(city)){
        console.log(obj[key])
        break
    }
}
//广东
复制代码

4.无规律 if-elseif

这样情况就是 if-elseif 没什么规则可寻。比如有需求是学生评选奖学金候选人活动

1.学生综合素质得分(quality>=90)90 以上,平均绩点 4.5 以上,成为一等奖学金候选人。

2.学生综合素质得分 80 以上(quality>=80),每科绩点均不低于 2.5 ,成为二等奖学金候选人。

3.学生综合素质得分不低于 75(quality>=75),获得过其他奖项荣誉,成为三等奖学金候选人。

let studentInfo={
    name:'守候',
    chinese:4.6,
    math:4.7,
    english:4,
    computer:4.8,
    quality:93,
    prizes:['搬砖','扫地']
}
let subjectAll=['chinese','math','english','computer']
function handle (student,subject) {
    function getAverage(){
        let sum=0
        subject.forEach(item=>sum+=student[item])  
        return sum/subject.length
    }
    function checkScore(){
      return subject.every(item => student[item]>2.5)
    }  
    if(student.quality>=90&&getAverage()>4.5){
        console.log('成为一等奖学金候选人')
        // 其他代码
    }
    else if(student.quality>=80&&checkScore()){
        console.log('成为二等奖学金候选人')
        // 其他代码
    }
    else if(student.quality>=75&&student.prizes.length>0){
        console.log('成为三等奖学金候选人')
        // 其他代码
    }
}

handle(studentInfo,subjectAll) //成为一等奖学金候选人
复制代码

代码看着很简单就完成了,但是实际开发中,if-elseif 的层级数量,每一个 if-elseif 判断逻辑和里面的执行操作的代码会远超过这个例子。那么 handle 这个方法的代码会很长,方法会变得巨大。对代码的可读性,维护性,复用性都有很多影响。如果需求有变动,还需要把整个函数的逻辑都理清楚。

上面的代码主要还是要处理好 if-elseif 。但是这些 if-else 并没有规矩可寻,所以只能全部都单独抽取出来,所有的都封装成函数。

function handle (student,subject) {
    function getAverage(){
        let sum=0
        subject.forEach(item=>sum+=student[item])  
        return sum/subject.length
    }
    function checkScore(){
      return subject.every(item => student[item]>2.5)
    }
    let rules=[
        {
            rule(){
                return student.quality>=90&&getAverage()>4.5
            },
            fn(){
                console.log('成为一等奖学金候选人')
                // 其他代码
            }
        },
        {
            rule(){
                return student.quality>=80&&checkScore()
            },
            fn(){
                console.log('成为二等奖学金候选人')
                // 其他代码
            }
        },
        {
            rule(){
                return student.quality>=75&&student.prizes.length>0
            },
            fn(){
                console.log('成为三等奖学金候选人')
                // 其他代码
            }
        }
    ]
    for(let item of rules){
        if(item.rule()){
            item.fn()
            // 执行完函数马上返回,不再执行下一次循环
            return
        }
    }
}
复制代码

这是只要有 item.fn() 有执行到,说明该同学已经成为了候选人,把返回回来就好。

这样一下,代码也完全符合 之前的 if-elseif 的逻辑。可能会有人有疑问,这样一改代码没减少反而变多了,但是实际上,这个代码已经是分为一块一块的,可读性和维护性已经有所改善,如果需要有那里改动,我们也不需要理会整个函数,只需要改动 rules

5.多重 if

还有一种情况是,几个 if-else 连着执行的代码,

比如有一个页面,能显示用户记录的足迹,也可以让用户选择记录自己的足迹,记录的范围可以是省,是市,是区。那么代码就需要实现一个省市区,多选联动。逻辑就是选了省,未必会选择市,但是选了市就必然会选了省,以此类推。下面简单写一下伪代码。

function initPostion(){
    //所有省份列表
    let provinceList=['广东','广西','海南','....'] 
    //已选省份下辖的所有城市
    let cityList=[] 
    //已选城市下辖的所有区
    let districtList=[] 
    
    //已选择省份
    let selectedProvinces=['广东','广西']
    //已选择城市
    let selectedCitys=['广州']
    //已选择区
    let selectedDistricts=['天河区']
    
    //如果选了省份
    if (selectedProvinces.length>0) {
        //根据 selectedProvinces 获取 cityList 的逻辑
        //其他代码
    }
    if (selectedCitys.length>0) {
        //根据 selectedCitys 获取 districtList 的逻辑
        //其他代码
    }
    
    //其他初始化显示的逻辑
}

复制代码

伪代码一写,想必有开发者已经看出问题了。所有的 if 都耦合在一起了, 而且 if 里面的代码逻辑可能会很多长。如果以后需求改了,要求选了国家,再选省市区,或者选择区之后,还能选择镇和村。到时候 if 会变多,initPostion 整体代码会变得巨大。维护起来会比较吃力,同时也容易出错。

解决这问题,可以把 if 拆分为函数

function initPostion(){
    //所有省份列表
    let provinceList=['广东','广西','海南','....'] 
    //已选省份下辖的所有城市
    let cityList=[] 
    //已选城市下辖的所有区
    let districtList=[] 
    
    //已选择省份
    let selectedProvinces=['广东','广西']
    //已选择城市
    let selectedCitys=['广州']
    //已选择区
    let selectedDistricts=['天河区']
    
    let handleObj={
        provinces(){
            //如果选了省份
            if (selectedProvinces.length>0) {
                //根据 selectedProvinces 获取 cityList 的逻辑
                //其他代码
            }
        },
        city(){
            if (selectedCitys.length>0) {
                //根据 selectedCitys 获取 districtList 的逻辑
                //其他代码
            }
        }
    }
    
    let handleFns=['provinces','city']
    
    for(let fnName of handleFns){
        handleObj[fnName]()
    }
    
    //其他初始化显示的逻辑
}
    
复制代码

可能这样看着代码是多了,但是管理起来会比原来的方案容易管理,每一块 if 都被拆分为一个函数,如果需要改动某一块代码,就改某一块就行了,不需要对其他的代码进行改动。如果有需求上的变动,就是改 handleObj 的属性函数,以及 handleFns 的顺序就行了。

参考链接

[浅析]特定场景下取代if-else和switch的方案

JavaScript 复杂判断的更优雅写法

小结

好了,关于 if-else 的代替和优化方案的第二篇文章到此为止了。这里再次强调,代替和优化 if-else 语句,建议是在特定场景下使用特定的方案进行。切记不能为了代替而代替,不能为了优化而优化。

如果之后还发现有另外的收获会第一时间再写文章。如果大家都该文章有什么建议,看法,或者文章有什么错误,改进的地方欢迎大家评论区留言

-------------------------华丽的分割线--------------------

想了解更多,和我交流,请添加我微信。或者关注我的微信公众号:守候书阁