写js逻辑判断,不要只知道用if-else和switch(代码片段)

lonelyxmas lonelyxmas     2022-12-26     743

关键词:

原文:写 JS 逻辑判断,不要只知道用 if-else 和 switch

我们在编写 JS 代码时,经常会遇到逻辑判断复杂的情况。一般情况下,可以用 if/else 或 switch 来实现多个条件判断,但会出现一个问题:随着逻辑复杂度的增加,代码中的 if/else 和 switch 会越来越臃肿。本文将带你尝试写出更优雅的判断逻辑。

比如说下面这样一段代码:

const onButtonClick = (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 重写如下:

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
  

好吧,看起来比 if/else 层次结构更清晰一些,细心的读者可能也发现了一个小窍门:case 2 和 case 3 的逻辑一样时,可以把前面的逻辑处理代码省略,case 2 会自动执行与 case 3 的逻辑。

不过,还有一个更简单的写法:

const actions = 
  ‘1‘: [‘processing‘, ‘IndexPage‘],
  ‘2‘: [‘fail‘, ‘FailPage‘],
  ‘3‘: [‘fail‘, ‘FailPage‘],
  ‘4‘: [‘success‘, ‘SuccessPage‘],
  ‘5‘: [‘cancel‘, ‘CancelPage‘],
  default: [‘other‘, ‘Index‘],


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‘]],
])

const onButtonClick = (status) => 
  let action = actions.get(status) || actions.get(‘default‘)
  sendLog(action[0])
  jumpTo(action[1])

使用 Map 代替 Object 有很多优点,Map 对象和普通对象有的区别是:

  • 一个对象通常有自己的原型,所以一个对象总是有一个“prototype”键
  • 对象的键只能是一个字符串或符号,但 Map 的键可以是任何值
  • 你可以通过使用 size 属性很容易得到 Map 中的键值对的数量,而一个对象中的键值对数量不能直接获取

现在我们来升级一下这个问题的难度。点击按钮时,不仅要判断状态,还要判断用户的身份。

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
    
  

从上面的例子中可以看到,当你的逻辑升级到双重判断的时候,你的判断力就会加倍,你的代码就会加倍。

如何才能让代码更干净利落呢?

这里有一个解决方案。

const actions = new Map([
  [‘guest_1‘, () => ],
  [‘guest_2‘, () => ],
  [‘guest_3‘, () => ],
  [‘guest_4‘, () => ],
  [‘guest_5‘, () => ],
  [‘master_1‘, () => ],
  [‘master_2‘, () => ],
  [‘master_3‘, () => ],
  [‘master_4‘, () => ],
  [‘master_5‘, () => ],
  [‘default‘, () => ],
])

const onButtonClick = (identity, status) => 
  let action = actions.get(`$identity_$status`) || actions.get(‘default‘)
  action.call(this)

上述代码的核心逻辑是。将两个判断条件拼接成一个字符串作为 Map 的键,然后在查询时直接查询对应字符串的值。当然,我们也可以在这里把 Map 改成 Object。

const actions = 
  guest_1: () => ,
  guest_2: () => ,
  //....

const onButtonClick = (identity, status) => 
  let action = actions[`$identity_$status`] || actions[‘default‘]
  action.call(this)

如果读者觉得把查询拼成一个字符串有点尴尬,还有另一个解决办法,那就是用一个 Map 对象作为 key。

const actions = new Map([
  [ identity: ‘guest‘, status: 1 , () => ],
  [ identity: ‘guest‘, status: 2 , () => ],
  //...
])
const onButtonClick = (identity, status) => 
  let action = [...actions].filter(([key, value]) => key.identity == identity && key.status == status)
  action.forEach(([key, value]) => value.call(this))

这里你也可以看到 Map 和普通对象的区别,其中 Map 可以用任何类型的数据作为键。现在让我们把它的难度再提高一点。如果对于 guest 身份来说,状态 1-4 的处理逻辑是一样的呢?

最坏的情况是这样的(代码大量重复):

const actions = new Map([
  [ identity: ‘guest‘, status: 1 , () => ],
  [ identity: ‘guest‘, status: 2 , () => ],
  [ identity: ‘guest‘, status: 3 , () => ],
  [ identity: ‘guest‘, status: 4 , () => ],
  [ identity: ‘guest‘, status: 5 , () => ],
  //...
])

更好的方法是把处理逻辑函数分离出来:

const actions = () => 
  const functionA = () => 
  const functionB = () => 
  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))

这对于日常需求来说已经足够了,但是说真的,函数 A 被引用了 4 次,还是有点烦人。

如果事情真的变得很复杂,比如身份有 3 种,状态有 10 种,你需要定义 30 个处理逻辑,其中很多处理逻辑都是一样的,这似乎让人无法接受。

而你可以这样做:

const actions = () => 
  const functionA = () =>  // 逻辑处理 A
  const functionB = () =>  // 逻辑处理 B
  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 而不是 Object 的优势比较明显,因为可以用正则式作为键。

如果需求变成:所有的对 guest 操作都需要发送一个日志埋点,不同状态的 guest 可能有不同的逻辑处理,那么我们可以写成如下:

const actions = () => 
  const functionA = () =>  // 逻辑处理 A
  const functionB = () =>  // 逻辑处理 B
  const functionC = () =>  // 发送日志 C
  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))

这样一来,公共逻辑和单个逻辑可以同时执行。

总结

本文讲到了八种 JS 逻辑判断的写法,包括:

  1. if/else
  2. switch
  3. 单一判断:存储在 Object 中
  4. 单一判断:存储在 Map 对象中
  5. 多重判断:将条件串联成一个字符串,存储在 Object 中
  6. 多重判断:将条件连成一个字符串,存储在 Map 对象中
  7. 多重判断:把条件作为对象存储在 Map 中
  8. 多重判断:把条件写成正则式存储在 Map 中

今天就分享到这里,愿你今后的编码生活不仅仅只有 if/else 或 switch。

写if-else不外乎两种场景:异常逻辑处理和不同状态处理。

写if-else不外乎两种场景:异常逻辑处理和不同状态处理。异常逻辑处理说明只能一个分支是正常流程,而不同状态处理都所有分支都是正常流程。第一个例子`if(obj!=null)`是异常处理,是代码健壮性判断,只有if里面才是正常的处理流... 查看详情

有啥完美的方法替代java中的if-else,switch-case

如果有大量同样的逻辑分支判断组,只是执行内容不一样,那么经典方法是用多态,建一个抽象基类,里面包含一个抽象函数。每个子类代表一个逻辑分支,实现抽象函数时,函数逻辑对应之前的if分支的内容。但是一定会保留... 查看详情

零散记录

...沉淀到自己手里头 把变化收敛在简单的地方,复杂的逻辑里不要加一堆if-else(比如for里面尽量少一大堆if-else)要收口,不要遍地开花技术手段,心理手段用别人的方法一定要关注类型有耐心,注意总 查看详情

零基础学c语言知识总结四:if-else判断语句和switch-case选择语句(代码片段)

...关键字if开头,跟上在括号()里边的是表示逻辑条件的表达式,然后是一对大括号中间是若干条语句,如果条件的逻辑表达式的结果不是零,那么就执行大括号中间的语句,否则就跳过不执行。if的返... 查看详情

求求你们了,别再写满屏的if/else了!(代码片段)

...想必都经历过这样的场景:刚开始自己写的代码很简洁,逻辑清晰,函数精简,没有一个if-else,可随着代码逻辑不断完善和业务的瞬息万变:比如需要对入参进行类型和值进行判断;这里要判断下对象是否为null;不同类型执行不... 查看详情

有啥完美的方法替代java中的if-else,switch-case

如果有大量同样的逻辑分支判断组,只是执行内容不一样,那么经典方法是用多态,建一个抽象基类,里面包含一个抽象函数。每个子类代表一个逻辑分支,实现抽象函数时,函数逻辑对应之前的if分支的内容。但是一定会保留... 查看详情

优雅的替换if-else语句

日常开发,if-else语句写的不少吧??当逻辑分支非常多的时候,if-else套了一层又一层,虽然业务功能倒是实现了,但是看起来是真的很不优雅,尤其是对于我这种有强迫症的程序"猿",看到这么多if-else,脑袋瓜子就嗡嗡的,总... 查看详情

else if 怎么写成逻辑语句?

】elseif怎么写成逻辑语句?【英文标题】:Howtowriteelseifaslogicalstatement?【发布时间】:2022-01-0712:57:13【问题描述】:我想写一个if-else语句作为逻辑语句。我知道:if(statement1)b=celseb=d可以写成:b=(statement1&&c)||(!statement1&&... 查看详情

我要快乐的度过我的实习期

...的一个月里,自己独立的完成了一个页面。刚开始的时候逻辑不怎么复杂,写起来还算得心应手,说的有点夸张了。基本知道用什么,就是记得不是太清,要去百度,要看参考手册。代码不足之处:1.动态创建元素熟悉。2.一开... 查看详情

写规范代码的意义------去除魔法数字

...拿到需求,看看需求有什么漏洞,然后罗列出需求的业务逻辑。实现常用功能基本没有技术障碍了。也知道写个好代码是那么难,那么有意义。很经常看到这样的代码.Stringname="xiaoming";if(name.eq 查看详情

怎么用cookie控制一个ip只执行一次js代码

不要完全照搬网上搜索来的,照搬请带详细说明,因为网页搜索出来的我搞不明白,我自己也搜索过的。谢谢了。cookie控制一个IP24小时只执行一次JS退出弹窗代码最好是JS文件代码(就是写在JS文件里面的外码)可以过绝大部分... 查看详情

如何优雅的用策略模式,取代臃肿的if-else嵌套,看这篇就够了(代码片段)

...公司推广入口很多,每一个下单来源在下单时都做特殊的逻辑处理,可能每两天就会加一个来源一、传统的实现方式那么按照传统的实现方式代码就是如下:publicclassOrderServiceImplimplementsIOrderService@OverridepublicStringhandle(OrderDTOdto)Str... 查看详情

if循环判断(代码片段)

if循环判断if-else循环的语法格式if逻辑判断句:?代码块#缩进表示所属关系else逻辑判断句:?代码块if和elif同时使用来做多层判断if逻辑判断式:代码块elif逻辑判断式:?代码块if的嵌套使用在if判断句中可以继续加入if的判断模块... 查看详情

小白写了一堆if-else,大神实在看不下去了,竟然用策略模式直接摆平了(代码片段)

...着如下几个问题来阅读本文:如何通过策略模式优化业务逻辑代码(可以根据自己从事的工作思考)使用策略模式优化if-else,会不会带来什么副作用呢?实现策略模式是否有更好的方式呢?1.策略模式如何优化代码解构要会带这... 查看详情

消除代码中的if-else/switch-case

...码中会有很多分支,而且分支下面的代码又有一些复杂的逻辑,相信很多人都喜欢用if-else/switch-case去实现。做的不好的会直接把实现的代码放在if-else/switch-case的分支之下:switch(type)casecase1:......break;casecase2:......break;casecase3:......b... 查看详情

缩短 Ruby 中的 if-else 结构

...经写过类似的东西,与我从VisualBasic6.0中知道的相同if-else逻辑,但我确信有更好的“Ruby方式”来编写它。你能告诉我它在Ruby世界中的样子吗?ifparams[:medication_name].nil?med_name=\'all 查看详情

约束逻辑程序中的奇怪警告和计算结果

】约束逻辑程序中的奇怪警告和计算结果【英文标题】:Strangewarningandcomputationresultinconstraintlogicprogram【发布时间】:2015-01-1312:35:39【问题描述】:首先,很抱歉发布整个程序,但我不知道问题是我不知道哪些部分是不相关的。... 查看详情

C# 数字和字母

...母或除数字以外的任何内容执行操作。不要这样做。如何判断字符串是字母还是数字?很简单,但我不会写这段代码。【问题讨论】:请说得更具体一些,你想知道它是只包含字母还是只包含数字?@atticae-很难说,但在我们的答... 查看详情