Write JS logical judgments, don't just know if-else and switch

Keywords: Attribute

Original: Write JS logical judgments, don't just know if-else and switch

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

For example, here's a code like this:

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')
  }
}

You can see the click logic for this button in the code.Do two things depending on the active state, send a log buried point and jump to the appropriate page.It's easy to think that this code can 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
  }
}

Well, it looks clearer than the if/else hierarchy, and careful readers may also find a trick: when case 2 and case 3 have the same logic, you can omit the previous logical processing code, and case 2 automatically executes the logic as case 3.

However, there is a simpler way to write:

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)
}

The code above does look cleaner, and the cleverness of this approach is that it takes the criteria as the object's attribute name and the processing logic as the object's attribute value.When you click on a button, this method is particularly useful for single-condition judgments, that is, logical judgments by looking up object attributes.

This method is good, but is there any other way to code it?Yes, we have!

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])
}

There are many advantages to using Map instead of Object. There are differences between Map objects and normal objects:

  • An object usually has its own prototype, so an object always has a "prototype" key
  • Object's key can only be a string or symbol, but Map's key can be any value
  • You can easily get the number of key-value pairs in a Map by using the size property, while the number of key-value pairs in an object cannot be obtained directly

Now let's upgrade the difficulty of this problem.When you click the button, you should not only judge 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
    }
  }
}

As you can see from the example above, when your logic is upgraded to double judgment, your judgment is doubled and your code is doubled.

How can I make the code cleaner?

There is 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)
}

The core logic of the above code is.Split the two criteria together into a string as the Map key, and then directly query the value of the corresponding string when querying.Of course, we can 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)
}

If readers find it embarrassing to spell a query into a string, another solution is to use a Map object as a 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))
}

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

Worst case scenario is this (code is very repetitive):

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

A better way 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))
}

That's enough for everyday needs, but to be honest, function A has been referenced four times and is still a bit annoying.

If things really get complicated, like three identities and ten states, you need to define 30 processing logic, many of which are the same, which seems unacceptable.

And you can do this:

const actions = () => {
  const functionA = () => {} // Logical Processing A
  const functionB = () => {} // Logical Processing 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))
}

The advantage of using Map instead of Object is obvious because you can use the regular form as the key.

If the requirement becomes that all guest operations need to send a log buried point, guests in different states may have different logical processing, we can write as follows:

const actions = () => {
  const functionA = () => {} // Logical Processing A
  const functionB = () => {} // Logical Processing 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))
}

In this way, both public logic and individual logic can be executed simultaneously.

summary

This paper describes the writing of eight JS logical judgments, including:

  1. if/else
  2. switch
  3. Single Judgment: Stored in Object
  4. Single Judgment: Stored in Map Objects
  5. Multiple Judgments: Concatenate conditions into a string and store them in Object
  6. Multiple Judgements: Conditions are concatenated into a string and stored in a Map object
  7. Multiple Judgements: Store conditions as objects in a Map
  8. Multiple Judgements: Write the conditions as regular and store them in the Map

Share this today and wish you more than if/else or switch in your future coding life.

Posted by php2MySQL on Thu, 28 May 2020 17:39:05 -0700