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