JavaScript has pretty normal control-flow statements that use blocks delineated by curly braces. There is an exception to this: the switch ... case
statement. The strange thing about switch ... case
is that you must include the keyword break at the end of each case to prevent control from falling through to the next case. Fall through is a trick that allows you to let more than one case be executed. Control will fall through automatically to the next case unless you explicitly tell it not to with break. However, like the optional semicolons and curly braces, it's possible to forget break when you really should have used it. When that happens, the bug is difficult to find because the code looks correct. For that reason, the break statement should never be left off of a case, even by design.
With that said, JavaScript has an elegant object-literal syntax and first-class functions, which makes it simple to create a keyed method lookup. The object you create for your method lookup is called an action object or command object and is used in many software design patterns.
Say you're creating a game where the nonplayer fight actions are selected based on an algorithm defined elsewhere and passed in to doAction as a string. The switch ... case
form looks like this:
function doAction(action) {
switch (action) {
case 'hack':
return 'hack';
break;
case 'slash':
return 'slash';
break;
case 'run':
return 'run';
break;
default:
throw new Error('Invalid action.');
break;
}
}
The method lookup version looks like this:
function doAction(action) {
var actions = {
'hack': function () {
return 'hack';
},
'slash': function () {
return 'slash';
},
'run': function () {
return 'run';
}
};
if (typeof actions[action] !== 'function') {
throw new Error('Invalid action.');
}
return actions[action]();
}
Or, for input grouping (a frequent use case for the fall-through feature): say you're writing a programming language parser, and you want to perform one action whenever you encounter a token that opens an object or array, and another whenever you encounter a token that closes them. Assume the following functions exist:
function handleOpen(token) {
return 'Open object / array.';
}
function handleClose(token) {
return 'Close object / array';
}
The switch ... case
form is:
function processToken (token) {
switch (token) {
case '{':
case '[':
handleOpen(token);
break;
case ']':
case '}':
handleClose(token);
break;
default:
throw new Error('Invalid token.');
break;
}
}
The method lookup version looks like this:
var tokenActions = {
'{': handleOpen,
'[': handleOpen,
']': handleClose,
'}': handleClose
};
function processToken(token) {
if (typeof tokenActions[token] !== 'function') {
throw new Error('Invalid token.');
}
return tokenActions[token](token);
}
At first glance, it might seem like this is more complicated syntax, but it has a few advantages:
- It uses the standard curly-bracket blocks used everywhere else in JavaScript.
- You never have to worry about remembering the break.
- Method lookup is much more flexible. Using an action object allows you to alter the cases dynamically at runtime, for example, to allow dynamically loaded modules to extend cases, or even swap out some or all of the cases for modal context switching.
- Method lookup is object oriented by definition. With
switch ... case
, your code is more procedural.
The last point is perhaps the most important. The switch statement is a close relative of the goto statement, which computer scientists argued for 20 years to eradicate from modern programming languages. It has the same serious drawback: almost everywhere I've seen switch ... case
used, I've seen it abused. Developers group unrelated functionality into overly-clever branching logic. In other words, switch ... case
tends to encourage spaghetti code, while method lookup tends to encourage well-organized, object-oriented code. It's far too common to find implementations of switch ... case
, which violate the principles of high cohesion and separation of concerns.
I was once a fan of switch ... case
as a better alternative to if ... else, but after becoming more familiar with JavaScript, I naturally fell into using method lookup instead. I haven't used switch ... case
in my code for several years. I don't miss it at all.
If you ever find yourself writing a switch statement, stop and ask yourself the following questions:
- Will you ever need to add more cases? (queue, stack, plug-in architecture)
- Would it be useful to modify the list of cases at runtime, for example, to change the list of enabled options based on context? (mode switching)
- Would it be useful to log the cases that get executed, for example, to create an undo/redo stack, or log user actions to your servers for analysis? (command manager)
- Are you referencing your cases by incrementing numbers, for example, case 1:, case: 2, etc.? (iterator target)
- Are you trying to group related inputs together with the fall through feature so that they can share code?
If you answered yes to any of these questions, there is almost certainly a better implementation that doesn't utilize switch or its slippery fall-through feature.
Just create an object:
actionTypes={ hack:"hack", slash:"slash", run:"run" }
and then call(for example)
const {action} = this.props; const currentAction=actionTypes[action]() || someDefaultFnc()