When we write JS code, we often encounter complex logical judgment. In general, you can use if/else or switch to implement multiple conditional judgments, but there is a problem: as logic complexity increases, the if/else and switch in your code become more bloated. This article will take you through the process of writing more elegant judgment logic.

For example, the following code:

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')}}Copy the code

You can see the button click logic in the code. Do two things based on the activity status, send the log burial point and jump to the appropriate page. It’s easy to imagine that this code could be rewritten with switch as follows:

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}}Copy the code

Well, it looks a little clearer than the if/else hierarchy, and the observant reader may have noticed a trick: if case 2 and case 3 have the same logic, you can omit the previous logic processing code, and case 2 will automatically execute the same logic as case 3.

But there’s an easier way to write it:

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)
}
Copy the code

The above code does look cleaner, but the nifty aspect of this approach is that it takes the judgment condition as the attribute name of the object and the processing logic as the attribute value of the object. When clicking buttons, this method is especially suitable for the case of single condition judgment, that is, logical judgment by the way of object attribute lookup.

That’s fine, but is there another way to code? Some!

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])}Copy the code

Using a Map instead of an Object has many advantages. The differences between a Map Object and a normal Object are as follows:

  • An object usually has its own prototype, so an object always has a “Prototype” key
  • The key of an object can only be a string or symbol, but the key of a Map can be any value
  • You can easily get the number of key-value pairs in a Map by using the size attribute, whereas the number of key-value pairs in an object cannot be obtained directly

Now let’s step this up a notch. When you click a button, you need to determine not only the status, but also the identity of the user.

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}}}Copy the code

As you can see from the example above, when you upgrade your logic to double judgment, your judgment doubles and your code doubles.

How do you make your code cleaner?

Here’s a solution.

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)}Copy the code

The core logic of the above code is. Concatenate the two criteria into a string as the key of the Map, and then directly query the value of the corresponding string during the query. Of course, we could also change Map to Object here.

const actions = {
  guest_1: () = > {},
  guest_2: () = > {},
  //....
}
const onButtonClick = (identity, status) = > {
  let action = actions[`${identity}_${status}`] || actions['default']
  action.call(this)}Copy the code

If the reader is a little embarrassed to assemble the query into a string, there is another solution, which is to use a Map object as the 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))}Copy the code

Here you can also see the difference between a Map and a normal object, where a Map can use any type of data as a key. Now let’s make it a little harder. What if the processing logic for states 1-4 is the same for guest?

The worst case looks like this (lots of code duplication) :

const actions = new Map([[{identity: 'guest'.status: 1 }, () = > {}],
  [{ identity: 'guest'.status: 2 }, () = > {}],
  [{ identity: 'guest'.status: 3 }, () = > {}],
  [{ identity: 'guest'.status: 4 }, () = > {}],
  [{ identity: 'guest'.status: 5 }, () = >{}]./ /...
])
Copy the code

A better approach is to separate the processing logic functions:

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))}Copy the code

This is enough for everyday needs, but seriously, function A is referenced 4 times, which is still A bit annoying.

If things get really complicated, like there are 3 identities and 10 states, you need to define 30 processing logics, many of which are the same, which seems unacceptable.

Here’s what you can do:

const actions = () = > {
  const functionA = () = > {} // handle A
  const functionB = () = > {} // select * from 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))}Copy the code

The advantage of using Map instead of Object is obvious because you can use regular keys.

If the requirement becomes: all operations on guest need to send a log burying point, and different states of guest may have different logical processing, then we can write as follows:

const actions = () = > {
  const functionA = () = > {} // handle A
  const functionB = () = > {} // select * from B
  const functionC = () = > {} // Send log 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))}Copy the code

In this way, the common logic and the single logic can be executed simultaneously.

conclusion

This article talks about eight kinds of JS logical judgment writing methods, including:

  1. if/else
  2. switch
  3. Single judgment: Stored in Object
  4. Single judgment: Stored in a Map object
  5. Multiple judgments: Concatenate conditions into a string, stored in Object
  6. Multiple judgments: Concatenate conditions into a string and store it in a Map object
  7. Multiple judgments: Store conditions as objects in a Map
  8. Multiple judgments: The condition is stored in a Map as a regular

That’s it for today. May your coding life be more than just if/else or switch.