我们在编写 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。

JS 写逻辑判断,不要只知道用 if-else 和 switch的更多相关文章

  1. js写的复制功能,只支持IE

    如果用js写,只能支持IE,如果想全支持,需要用jQuery的插件:jquery.zclip.js 下面是用js写的: var copyHref = function(){               ...

  2. 去它的h5,我还是用js写原生跨平台app吧

    智能手机功能越来越强大,已经在逐渐替代电脑的作用.百度.腾讯.阿里的移动端日活数也在逐步的赶上甚至超越电脑端用户.叫喊着“mobile first”的公司越来越多,App开发者应运而生,且队伍日趋庞大 ...

  3. 用Node.js写爬虫,撸羞羞的图片

    说到爬虫,很多人都认为是很高大上的东西.哇塞,是不是可以爬妹纸图啊,是不是可以爬小片片啊.答案就是对的.爬虫可以完成这些东西的操作.但是,作为一个正直的程序员,我们要在法律允许范围内用爬虫来为我们服务 ...

  4. js写插件教程深入

    原文地址:https://github.com/lianxiaozhuang/blog 转载请注明出处 js 写插件教程深入 1.介绍具有安全作用域的构造函数 function Fn(name){ t ...

  5. 原生js写Ajax

    //原生js写ajax就像打电话 //打电话分下面4步//1.拿出手机//2.拨号//3.说话//4.挺对方说话 //ajax也分下面4步//1.创建ajax对象//2.连接到服务器//3.发送请求( ...

  6. 原生JS写的ajax函数

    参照JQuery中的ajax功能,用原生JS写了一个ajax,功能相对JQuery要少很多,不过基本功能都有,包括JSONP. 调用的方式分为两种: 1. ajax(url, {}); 2. ajax ...

  7. 使用 Node.js 写一个代码生成器

    背景 第一次接触代码生成器用的是动软代码生成器,数据库设计好之后,一键生成后端 curd代码.之后也用过 CodeSmith , T4.目前市面上也有很多优秀的代码生成器,而且大部分都提供可视化界面操 ...

  8. 前端与编译原理——用JS写一个JS解释器

    说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...

  9. php框架tp3.2.3和js写的微信分享功能心得,分享的标题内容图片自定义

    https://blog.csdn.net/weixin_42231483/article/details/81585322 最近用PHP的tp3.2.3框架和js写的微信分享功能心得,分享的标题内容 ...

随机推荐

  1. Vue项目中jQuery的引入

    1.安装jQuery依赖 npm install jquery --save-dev 2.在webpack.base.conf.js头部加入如下代码 var webpack = require(&qu ...

  2. python 多进程处理 multiprocessing模块

    前提: 有时候一个用一个进程处理一个列表中的每个元素(每个元素要传递到一个函数中进行处理),这个时候就要用多进程处理 1 现场案例: 我有一个[ip1,ip2,ip3,.......]这样的列表,我要 ...

  3. Image Filter and Recover

    这是CS50的第四次大作业,顺便学习了图像的入门知识. 基础 黑白图(bitmap)的每个像素点只能取值0/1,1代表白色,0代表黑色. 常见的图片格式有JPEG/PNG/BMP,这些格式都支持RGB ...

  4. 贪心--HDU 2021 发工资咯

    Description 作为杭电的老师,最盼望的日子就是每月的8号了,因为这一天是发工资的日子,养家糊口就靠它了,呵呵,但是对于学校财务处的工作人员来说,这一天则是很忙碌的一天,财务处的小胡老师最近就 ...

  5. bzoj4318 OSU!和bzoj 3450 Tyvj1952 Easy

    这俩题太像了 bzoj 3450 Tyvj1952 Easy Description 某一天WJMZBMR在打osu~~~但是他太弱逼了,有些地方完全靠运气:( 我们来简化一下这个游戏的规则 有n次点 ...

  6. varnish 项目实战

    1.工作原理 在当前主流的Web服务架构体系中,Cache担任着越来越重要的作用.常见的基于浏览器的C/S架构,Web Cache更是节约服务器资源的关键.而最近几年由FreeBSD创始人之一Kamp ...

  7. dp D. Caesar's Legions

    https://codeforces.com/problemset/problem/118/D 这个题目有点思路,转移方程写错了. 这个题目看到数据范围之后发现很好dp, dp[i][j][k1][k ...

  8. Linux查看redis占用内存的方法

    redis-cli auth 密码info # Memory used_memory:13490096 //数据占用了多少内存(字节) used_memory_human:12.87M //数据占用了 ...

  9. MySQL 查询当天、本周,本月、上一个月的数据

    mysql查询当天的所有信息: SELECT * FROM 表名 WHERE year(时间字段名)=year(now()) and month(时间字段名) = month(now()) and d ...

  10. linux --批量修改文件内容

    由于目前测试的BIOS有一个option 发生了改变,因此我们需要在之前写好的脚本上进行修改,将旧的option 改为新的选项,因此在此处用到了批量修改文件中的内容: 1. perl 命令替换: pe ...