zoukankan      html  css  js  c++  java
  • 【转】转自微信公众号 JavaScript 复杂判断的更优雅写法

    与微信公众号看到一篇js复杂判断的文章,对我启发很大,故转到博客园以供后期不断学习并应用于项目。原文地址:https://mp.weixin.qq.com/s/ClFDRj4MnAxv1dJ5VWKSJQ

    前提

    我们编写js代码时经常遇到复杂逻辑判断的情况,通常大家可以用if/else或者switch来实现多个条件判断,但这样会有个问题,随着逻辑复杂度的增加,代码中的if/else/switch会变得越来越臃肿,越来越看不懂,那么如何更优雅的写判断逻辑,本文带你试一下。

    举个例子

    先看一段代码

    通过代码可以看到这个

     1 constonButtonClick=(status)=>{
     2     if(status==1){
     3         sendLog('processing')jumpTo('IndexPage')
     4     }elseif(status==2){
     5         sendLog('fail')jumpTo('FailPage')
     6     }elseif(status==3){
     7         sendLog('fail')jumpTo('FailPage')
     8     }elseif(status==4){
     9         sendLog('success')jumpTo('SuccessPage')
    10     }elseif(status==5){
    11         sendLog('cancel')jumpTo('CancelPage')
    12     }else{
    13         sendLog('other')jumpTo('Index')
    14     }
    15 }
    View Code

    按钮的点击逻辑:根据不同活动状态做两件事情,发送日志埋点和跳转到对应页面,大家可以很轻易的提出这段代码的改写方案,switch出场:

     1 const onButtonClick =
     2 
     3 (status) = >{
     4 
     5     switch
     6 
     7     (status) {
     8 
     9     case
    10 
    11         1:
    12         sendLog('processing') jumpTo('IndexPage')
    13 
    14         break
    15 
    16     case
    17 
    18         2:
    19 
    20     case
    21 
    22         3:
    23         sendLog('fail') jumpTo('FailPage')
    24 
    25         break
    26 
    27     case
    28 
    29         4:
    30         sendLog('success') jumpTo('SuccessPage')
    31 
    32         break
    33 
    34     case
    35 
    36         5:
    37         sendLog('cancel') jumpTo('CancelPage')
    38 
    39         break
    40 
    41     default:
    42         sendLog('other') jumpTo('Index')
    43 
    44         break
    45 
    46     }
    47 }
    View Code

    嗯,这样看起来比if/else清晰多了,细心的同学也发现了小技巧,case 2和case 3逻辑一样的时候,可以省去执行语句和break,则case 2的情况自动执行case 3的逻辑。

    这时有同学会说,还有更简单的写法:

     1 const actions ={
     2     '1':['processing', 'IndexPage'],
     3     '2':['fail', 'FailPage'],
     4     '3':['fail', 'FailPage'],
     5     '4':['success', 'SuccessPage'],
     6     '5':['cancel', 'CancelPage'],
     7     'default':['other', 'Index'],
     8 }
     9 const onButtonClick =
    10 (status) = >{
    11     let action = actions[status]|| actions['default'],
    12     logName = action[0],
    13     pageName = action[1] 
    14     sendLog(logName) 
    15     jumpTo(pageName)
    16 }
    View Code

    上面代码确实看起来更清爽了,这种方法的聪明之处在于:将判断条件作为对象的属性名,将处理逻辑作为对象的属性值,在按钮点击的时候,通过对象属性查找的方式来进行逻辑判断,这种写法特别适合一元条件判断的情况。

    是不是还有其他写法呢?有的:

     1 const actions = new Map([ 
     2     [1, ['processing', 'IndexPage']], 
     3     [2, ['fail', 'FailPage']], 
     4     [3, ['fail', 'FailPage']], 
     5     [4, ['success', 'SuccessPage']], 
     6     [5, ['cancel', 'CancelPage']], 
     7     ['default', ['other', 'Index']]]) 
     8 const onButtonClick = (status) => 
     9 {
    10     let action = actions.get(status) || actions.get('default') 
    11     sendLog(action[0]) 
    12     jumpTo(action[1])
    13 }
    View Code

    这样写用到了es6里的Map对象,是不是更爽了?Map对象和Object对象有什么区别呢?

    1. 一个对象通常都有自己的原型,所以一个对象总有一个"prototype"键。

    2. 一个对象的键只能是字符串或者Symbols,但一个Map的键可以是任意值。

    3. 你可以通过size属性很容易地得到一个Map的键值对个数,而对象的键值对个数只能手动确认。

    我们需要把问题升级一下,以前按钮点击时候只需要判断status,现在还需要判断用户的身份:

     1 const onButtonClick = (status, identity) => 
     2 {
     3     if(identity == 'guest')
     4     {
     5         if(status == 1) { }
     6         else if (status == 2) { }
     7         else if (status == 3) { }
     8         else if (status == 4) { }
     9         else if (status == 5) { }
    10         else { }
    11     }
    12     else if (identity == 'master') 
    13     {
    14         if(status == 1) { }
    15         else if (status == 2) { }
    16         else if (status == 3) { }
    17         else if (status == 4) { }
    18         else if (status == 5) { }
    19         else { }
    20     }
    21 }
    View Code

    原谅我不写每个判断里的具体逻辑了,因为代码太冗长了。

    原谅我又用了if/else,因为我看到很多人依然在用if/else写这种大段的逻辑判断。

    从上面的例子我们可以看到,当你的逻辑升级为二元判断时,你的判断量会加倍,你的代码量也会加倍,这时怎么写更清爽呢?

     1 const actions = new Map([ 
     2     ['guest_1', () => {}], 
     3     ['guest_2', () => {}], 
     4     ['guest_3', () => {}], 
     5     ['guest_4', () => {}], 
     6     ['guest_5', () => {}], 
     7     ['master_1', () => {}], 
     8     ['master_2', () => {}], 
     9     ['master_3', () => {}], 
    10     ['master_4', () => {}], 
    11     ['master_5', () => {}], 
    12     ['default', () => {}], ])
    13 
    14 /**
    15  * 按钮点击事件
    16  * @param {string} identity 身份标识:guest客态 master主态
    17  * @param {number} status 活动状态:1 开团进行中 2 开团失败 3 开团成功 4 商品售罄 5 有库存未开团
    18  */
    19 const onButtonClick = (identity, status) => 
    20 {
    21     let action = actions.get(`$ {identity}_${status}`) || actions.get('default') 
    22     action.call(this)
    23 }
    View Code

    上述代码核心逻辑是:把两个条件拼接成字符串,并通过以条件拼接字符串作为键,以处理函数作为值的Map对象进行查找并执行,这种写法在多元条件判断时候尤其好用。

    当然上述代码如果用Object对象来实现也是类似的:

    1 const actions = {
    2     'guest_1' : () => {}, 
    3     'guest_2' : () => {}, 
    4     //....
    5 }
    6 const onButtonClick = (identity, status) => {
    7     let action = actions[`${identity}_${status}`] || actions['default'] 
    8     action.call(this)
    9 }
    View Code

    如果有些同学觉得把查询条件拼成字符串有点别扭,那还有一种方案,就是用Map对象,以Object对象作为key:

     1 const actions = new Map([ 
     2     [{identity : 'guest', status : 1},() => {}], 
     3     [{identity : 'guest', status : 2},() => {}], 
     4     //...
     5 ]) 
     6 const onButtonClick = (identity, status) => 
     7 {
     8     let action = [...actions].filter(([key, value]) => (key.identity == identity && key.status == status)) 
     9     action.forEach(([key, value]) => value.call(this))
    10 }
    View Code

    是不是又高级了一点点?

    这里也看出来Map与Object的区别,Map可以用任何类型的数据作为key。

    我们现在再将难度升级一点点,假如guest情况下,status1-4的处理逻辑都一样怎么办,最差的情况是这样:

    1 const actions = new Map([ 
    2 [{identity : 'guest', status : 1},() => {}], 
    3 [{identity : 'guest', status : 2},() => {}],
    4 [{identity : 'guest', status : 3},() => {}],
    5 [{identity : 'guest', status : 4},() => {}],
    6 [{identity : 'guest', status : 5},() => {}], 
    7 //...
    8 ])
    View Code

    好一点的写法是将处理逻辑函数进行缓存:

     1 const actions = () => 
     2 {
     3     const functionA = () => {}
     4     const functionB = () => {}
     5     return new Map([ 
     6     [{identity : 'guest', status : 1},functionA], 
     7     [{identity : 'guest', status : 2},functionA], 
     8     [{identity : 'guest', status : 3},functionA], 
     9     [{identity : 'guest', status : 4},functionA], 
    10     [{identity : 'guest', status : 5},functionB], ])
    11 }
    12 const onButtonClick = (identity, status) => 
    13 {
    14     let action = [...actions()].filter(([key, value]) => (key.identity == identity && key.status == status)) 
    15     action.forEach(([key, value]) => value.call(this))
    16 }
    View Code

    这样写已经能满足日常需求了,但认真一点讲,上面重写了4次functionA还是有点不爽,假如判断条件变得特别复杂,比如identity有3种状态,status有10种状态,那你需要定义30条处理逻辑,而往往这些逻辑里面很多都是相同的,这似乎也是笔者不想接受的,那可以这样实现:

     1 const actions = () => 
     2 {
     3     const functionA = () => {}
     4     const functionB = () => {}
     5     return new Map([ 
     6         [/^guest_[1-4]$/, functionA], 
     7         [/^guest_5$/, functionB], 
     8     ])
     9 }
    10 const onButtonClick = (identity, status) => 
    11 {
    12     let action = [...actions()].filter(([key, value]) => (key.test(`${identity}_${status}`))) 
    13     action.forEach(([key, value]) => value.call(this))
    14 }
    View Code

    这里Map的优势更加凸显,可以用正则类型作为key了,这样就有了无限可能,假如需求变成,凡是guest情况都要发送一个日志埋点,不同status情况也需要单独的逻辑处理,那我们可以这样写:

     1 const actions = () => 
     2 {
     3     const functionA = () => {}
     4     const functionB = () => {}
     5     const functionC = () => {}
     6     return new Map([ 
     7         [/^guest_[1-4]$/, functionA], 
     8         [/^guest_5$/, functionB], 
     9         [/^guest_.*$/, functionC], 
    10     ])
    11 }
    12 
    13 const onButtonClick = (identity, status) => 
    14 {
    15     let action = [...actions()].filter(([key, value]) => (key.test(`${identity}_${status}`))) 
    16     action.forEach(([key, value]) => value.call(this))
    17 }
    View Code

    也就是说利用数组循环的特性,符合正则条件的逻辑都会被执行,那就可以同时执行公共逻辑和单独逻辑,因为正则的存在,你可以打开想象力解锁更多的玩法,本文就不赘述了。

    总结

    本文已经教你了8种逻辑判断写法,包括:

    1. if/else

    2. switch

    3. 一元判断时:存到Object里

    4. 一元判断时:存到Map里

    5. 多元判断时:将condition拼接成字符串存到Object里

    6. 多元判断时:将condition拼接成字符串存到Map里

    7. 多元判断时:将condition存为Object存到Map里

    8. 多元判断时:将condition写作正则存到Map里

    至此,本文也将告一段落,愿你未来的人生里,不只是有if/else/switch。

  • 相关阅读:
    172. Factorial Trailing Zeroes
    96. Unique Binary Search Trees
    95. Unique Binary Search Trees II
    91. Decode Ways
    LeetCode 328 奇偶链表
    LeetCode 72 编辑距离
    LeetCode 226 翻转二叉树
    LeetCode 79单词搜索
    LeetCode 198 打家劫舍
    LeetCode 504 七进制数
  • 原文地址:https://www.cnblogs.com/PearlRan/p/10900755.html
Copyright © 2011-2022 走看看