JavaScript’s switch statement is one of the few things I find
hard to remember the syntax for (so glad VS Code has autocomplete). It
also feels a little bit out of place syntactically, as it’s the only thing
that doesn’t use curly braces and you need to remember to
break for every case. Moreover, its performance
is less than stellar as its control flow is procedural.
Luckily, JavaScript’s object literals are a pretty good alternative for
most switch statement use-cases I can think of. The idea is
to define an object with a key for each case you would have
in a switch statement. Then you can access its value directly
using the expression you would pass to the switch statement.
let fruit = 'oranges';
switch (fruit) {
case 'apples':
console.log('Apples');
break;
case 'oranges':
console.log('Oranges');
break;
}
// Logs: 'Oranges'
const logFruit = {
'apples': () => console.log('Apples'),
'oranges': () => console.log('Oranges')
};
logFruit[fruit](); // Logs: 'Oranges'
While this is infinitely more readable and less verbose, it’s also
significantly faster. However, we haven’t yet addressed the elephant in
the room: the default case. To handle it, we can just add a
'default' key and check if the expression’s value exists in
our object.
let fruit = 'strawberries';
switch (fruit) {
case 'apples':
console.log('Apples');
break;
case 'oranges':
console.log('Oranges');
break;
default:
console.log('Unknown fruit');
}
// Logs: 'Unknown fruit'
const logFruit = {
'apples': () => console.log('Apples'),
'oranges': () => console.log('Oranges'),
'default': () => console.log('Unknown fruit')
};
(logFruit[fruit] || logFruit['default'])(); // Logs: 'Unknown fruit'
Finally, our object literal replacement should be able to handle falling
through cases, similar to what happens when there’s no
break statement. This is a matter of simply extracting and
reusing logic in the object literal.
let fruit = 'oranges';
switch (fruit) {
case 'apples':
case 'oranges':
console.log('Known fruit');
break;
default:
console.log('Unknown fruit');
}
// Logs: 'Known fruit'
const knownFruit = () => console.log('Known fruit');
const unknownFruit = () => console.log('Unknown fruit');
const logFruit = {
'apples': knownFruit,
'oranges': knownFruit,
'default': unknownFruit
};
(logFruit[fruit] || logFruit['default'])(); // Logs: 'Known fruit'
To wrap this all up, we can generalize and extract this logic into a
simple reusable function. We will supply it with the lookup object and an
optional name for the default case (we’ll default to
_default to avoid any conflicts). This function will in turn
return a function with the appropriate lookup logic and we can use it to
replace any switch statement.
const switchFn = (lookupObject, defaultCase = '_default') =>
expression => (lookupObject[expression] || lookupObject[defaultCase])();
const knownFruit = () => console.log('Known fruit');
const unknownFruit = () => console.log('Unknown fruit');
const logFruit = {
'apples': knownFruit,
'oranges': knownFruit,
'default': unknownFruit
};
const fruitSwitch = switchFn(logFruit, 'default');
fruitSwitch('apples'); // Logs: 'Known fruit'
fruitSwitch('pineapples'); // Logs: 'Unknown fruit'