条件语句优化面面观

1,955 阅读8分钟

前言

在前端的日常开发中,根据不同条件执行不同的代码逻辑是最常见不过的,而我们最常用的逻辑也就是if,else了。当我们的条件变得越来越复杂的时候,我们会发现代码会出现下面的几种情况,让我们后续维护工作以及拓展变得越发困难。

备注:本文并不是说所有的if,else的语句都有必要优化,而是指对后续开发以及维护已经造成困扰,或者代码不严谨的风险增大时,我们可以考虑的一个方向。

特别说明:本文参考了掘金至今为止发布的所有有关if/else的文章,也是希望大家在学习了解一些知识时尽量全面的分析某个知识点,判断原来的场景,以及这种代码技巧究竟好在哪里,我为什么要用。

经典条件语句写法

// 情况一 :多种判断临界值
if(a === 1){
   code xxx
 } else if(a>==2 && a<==10){
   code xxx
 } else if(a>==11){
   code xxx
 }

// 情况二 :嵌套结构
if(age>18){
   if(money>==1000){
	}else{
    }
 } else if(age <== 10){
 	if(money>==1000){
	}else{
    }
 }
 
//情况三 :因为参与条件的不同值情况,
//比如下面这种2个参数,布尔型2个值,那么组合起来便有4种,如果每个都不同,你最少要写4个语句块
 if(a && b){
 }else if(!a && b){
 }else if(a && !b){
 }else{
 }


//情况四 :是针对函数而言的,最近比较流行的写法,当满足返回条件时,尽早返回
if(case 1){
code xxx;
return 
}
if(case 2){
code xxx;
return 
}
if(case 3){
code xxx;
return 
}

之所以列出上面几种场景,是为了下面能够更加深入的针对不同情况列出针对性更强的解决方案。

初级:代码技巧

在很多有过一点经验的前端那里,我们可以经常看到一些代码技巧,而这些代码技巧在条件语句优化时同样适用。

switch case

switch case大概是我们最容易想到的优化的方式之一,而且没有理解成本和使用成本,限制就是对case比较限定,需要根据一个关键的值切换策略,而不能是多个值,所以如果你想switch达到比较好的效果,可能有两种前提:1你的限定条件本来就是一个关键值 2 就是你在决定switch的时候,已经把多个值的判断转换成了一个值(至于如何转换下面一条就可以看到了)。

//这里需要提醒给大家的是
// case中可以写条件判断
let a =1 
switch(true){
  case a<10:{
  	console.log(0);
    break;
  }
  case a === 10:{
  	console.log(10);
    break;
  }  
}
// 多个case可以执行同一个代码段
switch (a){
  case 2:case4:{
  }
  default:{
  } 
}

// 如果不写break,会执行的代码段1,555会分别打印出来,
//也就是从第一个符合条件的开始,不断执行逻辑,除非后续遇到break,你需要注意这个盲点,也可以某种情况下利用
let a = 10;
switch(a){
  case 3:{console.log(4444)}
  case 10:{console.log(1);}
  case 2:{console.log(555);break}
  default:{console.log(a);}
}

抽离判断条件定义函数

在上面的逻辑中,我们可以看到有大量的针对关键条件进行逻辑判断的代码,那么首先,对于不同条件下的代码段而言, 你只是需要一个判断的结果,然后执行某段代码。从这个出发点来说,我们可以将所有的判断语句定义为函数,这个函数的作用就是通过输入条件,返回一个可以决定使用何种方案,执行什么代码的唯一值。

我们拿上面的第三种情况举例,实际我们改写后是这样的:

//你可以将所有的参数收集,综合写一个判断,返回使用哪种代码的唯一标识
const judgeStragety = (a,b){
 if(a && b){
 	return 'case1'
 }else if(!a && b){
   return 'case2'
 }else if(a && !b){
     return 'case3'
 }else{
    return 'case4'
 }
}

//执行代码中,你的使用便成了,然后执行对应策略标识的代码段便可以
let stragety = judgeStragety(a,b)

// 甚至有些情况下 你觉得某些判断条件都是不同的,你甚至可以依据某个判断条件抽离单独的函数
// 这样的好处便是,更解耦,
//别人也更能通过你的函数名,判断出这个逻辑语句的真正价值,不然 q === 1这种,真的别人看不懂
if(judgeA(a,b,c)){
}
if(judgeB(b,f)){
} 

抽离代码段公共逻辑,封装函数

函数化编程是前端的基本思维之一,函数作为编程的一等公民,我们时刻要记得针对冗余、具有优化空间的代码进行封装。

比如我今天看到某些人代码写成这样,明显的代码冗余,既然代码具有共同逻辑,就该把这部分抽离为一个函数,后续也可以让代码可维护性更强。

if(a === 1){
  console.log(1111)
	alert(10)
}else if(a === 2){
  console.log(2222)
	alert(20)
}else{
	code xxx
}

const action = code=>{
 console.log(1111*code)
  alert(10*code)
}


if(a===1){
action(1)
}else if(a===2){
action(2)
}else {
code xxx
}

短路与&&,逻辑或||

这个部分在我另外一篇文章奇技淫巧中有非常详细的技巧描述,可以点击查看:www.yuque.com/robinson/js…

比如:逻辑与执行函数, a && b && fn(),代替 if(a && b){fn()}

比如逻辑或,进行默认赋值 ,let msg = error.msg || '默认展示信息'

三目判断

三目判断也是减少条件判断的一种,因为其通过赋值语句的方式已经将条件不同时,两个处理结果分别给出。

let desc = age > 18 ? '成年人' :'未成年'

中级:数据结构+函数化编程

数据结构

针对一些常用的数据结构,有很多可以用来简化我们的常见的逻辑语句以及代码执行策略。

// 原来
if(sex === 0){
 desc = '女'
}else if(sex === 1){
	desc = '男'
}else{
	desc = ''
}
// 使用字典来做1对1 的对应关系
let sexDict = {
 0:'女',
  1:'男'
}
let desc = sexDict[sex]

// 使用数组收集多个条件值
//原来 
if(a === 0 || a ===4){
  code xxx
}
// 现在 
const statusArr = [0,4]
if(statusArr.includes(status)){

}


//使用map收集复杂值 map的价值就在于可以设置key为正则、对象等等
let vaildScore = 'score_2'
let scoreHandler = new Map()
scoreHandler.set(/^score_[1-9]{1}/,()=>console.log('vip1'))
scoreHandler.set(/^score_0/,()=>console.log('vip0'))
scoreHandler.forEach(function(value,key){
  if(key.test(vaildScore)){
    value()
    return
  }
})

函数编程

  • 纯粹性
  • 柯里化
  • 不可变性
  • 函数组合

高级 :设计模式

策略模式

针对不同条件,利用不用算法,得出一个结论。语雀链接:www.yuque.com/robinson/de…

const PriceStrategy = function(){
// 内部算法对象
    const stragtegy = {
       return30:function(price){
        },
       return60:function(price){
        },
    }
// 策略算法调用接口
   return function(cheaperLevel,price){
       return stragtegy[cheaperLevel] && stragtegy[cheaperLevel](price)
   }
}

// 使用方式
let price = PriceStrategy('return30','321.56')
console.log(price)

状态模式

针对不同条件,可以化解为各种状态,执行不同状态的封装函数,状态模式地址:www.yuque.com/robinson/de…

let status = light.status
switch(status){
    case 'on':console.log('light is on ,we can read books');
    break;
    case 'off':console.log('light is off ,we can not see anything');
    break;
     case 'error':console.log('light is error ,we can not do any operation');
    break;
}

function lightTip(status){
    case 'on':console.log('light is on ,we can read books');
    break;
    case 'off':console.log('light is off ,we can not see anything');
    break;
     case 'error':console.log('light is error ,we can not do any operation');
    break;
}
// 获取状态执行操作
let status = light.status 
lightTip(status)
// 其他逻辑里更改状态执行
light.status = 'off'
// 继续执行
let status = light.status 
lightTip(status)

//思考抽象为电灯状态
// 电灯类
class light{
  setState(state){
    this.state = state
  }
  getState(){
      return this.state.status
  }
  request(){
    return this.state.handler()
  }
} 
// 状态类
class lightState{
  constructor(){
    this.status = ''
  }
  handler(context){
    console.error('不在任何有效状态')
  }
}
// 具体状态实现 启动状态
class onState extends lightState{
  constructor(){
    super();
    this.status='on'
  }
  handler(){
    console.log('light is on ,we can read books')
  }
}
// 关闭状态实现
class offState extends lightState{
  constructor(){
    super();
    this.status='off'
  }
  handler(){
    console.log('light is off ,we can not see anything')
  }
}

let lightDemo = new light()
lightDemo.setState(new onState())
lightDemo.request()

lightDemo.setState(new offState())
lightDemo.request()

职责链模式 --- 循环匹配

定义n种判断条件以及执行函数,当不确定具体执行哪种时,循环判断所有队列中是否有符合条件的可用函数,优点,灵活方便,可以灵活的追加或者减少函数;缺点,增加了不必要的函数运行成本,不能尽早返回。

image.png

let dutyArr = []
dutyArr.push({
	match:(a,b)=>{
},
  action:()=>{
 }           
})

dutyArr.push({
	match:(a,b)=>{
},
  action:()=>{
 }           
})

dutyArr.forEach(function(item){
  if(item.natch(ab)){
    item.action()
  }
})

小结

管中窥豹,可见一斑。我们学习编程,要善于从一个点出发,思考各个可能,各种使用场景,适合使用什么技巧,而非是见一个知道一个用一个。

本文提供的是从小到大,从初级到高级,我们在处理代码逻辑时的通用思维方式,本文所整理的思路也不是最完整、最权威的,只是帮助大家在相应一个技术主题的时候能够像更系统或者更具体的方式多探索一些。

友情链接

我的js语雀小册:www.yuque.com/robinson/js…