In SQL we can see if a string is in a list like so:
Column IN ('a', 'b', 'c')
What’s a good way to do this in JavaScript? It’s so clunky to do this:
if (expression1 || expression2 || str === 'a' || str === 'b' || str === 'c') {
// do something
}
And I’m not sure about the performance or clarity of this:
if (expression1 || expression2 || {a:1, b:1, c:1}[str]) {
// do something
}
Or one could use the switch function:
var str = 'a',
flag = false;
switch (str) {
case 'a':
case 'b':
case 'c':
flag = true;
default:
}
if (expression1 || expression2 || flag) {
// do something
}
But that is a horrible mess. Any ideas?
In this case, I have to use Internet Explorer 7 as it’s for a corporate intranet page. So ['a', 'b', 'c'].indexOf(str) !== -1 won’t work natively without some syntax sugar.
ES6 (ES2015) and up
If you’re using ECMAScript 6 (a.k.a. ES2015) or higher, the cleanest way is to construct an array of the items and use
Array.includes:This has some inherent benefits over
indexOfbecause it can properly test for the presence ofNaNin the list, and can match missing array elements such as the middle one in[1, , 2]toundefined. It also treats+0and-0as equal.includesalso works on JavaScript typed arrays such asUint8Array.If you’re concerned about browser support (such as for IE or Edge), you can check
Array.includesat CanIUse.Com, and if you want to target a browser or browser version that’s missingincludes, you’ll need to transpile to a lower ECMAScript version using a tool such as Babel, or include a polyfill script in the browser, such as those available at polyfill.io.Higher Performance
Note that there is no guarantee that
Array.includes()execution time won’t scale with the number of elements in the array: it can have performance O(n). If you need higher performance, and won’t be constructing the set of items repeatedly (but will be repeatedly checking if the items contain some element), you should use aSetbecause the ES spec requires implementations ofSet(andMapas well) to be sub-linear for reads:Note that you can pass in any iterable item to the
Setconstructor (anything that supports for…of). You can also convert aSetto an array usingArray.from(set)or by spreading it[...set].Without An Array
This is not really recommended, but you could add a new
isInListproperty to strings as follows:Then use it like so:
You can do the same thing for
Number.prototype.Note that
Object.definePropertycannot be used in IE8 and earlier, or very old versions of other browsers. However, it is a far superior solution toString.prototype.isInList = function() { ... }because using simple assignment like that will create an enumerable property onString.prototype, which is more likely to break code.Array.indexOf
If you are using a modern browser,
indexOfalways works. However, for IE8 and earlier you’ll need a polyfill.If
indexOfreturns -1, the item is not in the list. Be mindful though, that this method will not properly check forNaN, and while it can match an explicitundefined, it can’t match a missing element toundefinedas in the array[1, , 2].Polyfill for
indexOforincludesin IE, or any other browser/version lacking supportIf you don’t want to use a service like polyfill.io as mentioned above, you can always include in your own source code standards-compliant custom polyfills. For example, the CoreJs library has an implementation of
indexOf.In this situation where I had to make a solution for Internet Explorer 7, I "rolled my own" simpler version of the
indexOf()function that is not standards-compliant:Notes On Modifying Object Prototypes
However, I don’t think modifying
String.prototypeorArray.prototypeis a good strategy long term. Modifying object prototypes in JavaScript can lead to serious bugs. You need to decide whether doing so is safe in your own environment. Of primary note is that iterating an array (when Array.prototype has added properties) withfor ... inwill return the new function name as one of the keys:Your code may work now, but the moment someone in the future adds a third-party JavaScript library or plugin that isn’t zealously guarding against inherited keys, everything can break.
The old way to avoid that breakage is, during enumeration, to check each value to see if the object actually has it as a non-inherited property with
if (arr.hasOwnProperty(x))and only then work with thatx.The new ES6 ways to avoid this extra-key problem are:
Use
ofinstead ofin,for (let x of arr). However, depending on the output target and the exact settings/capabilities of your down-leveling transpiler, this may not be reliable. Plus, unless you can guarantee that all of your code and third-party libraries strictly stick to this method, then for the purposes of this question you’ll probably just want to useincludesas stated above.Define your new properties on the prototype using
Object.defineProperty(), as this will make the property (by default) non-enumerable. This only truly solves the problem if all the JavaScript libraries or modules you use also do this.Be Aware of One Last Issue
Last, be aware that while polyfills make sense, and modifying object prototypes is a useful strategy, there can occasionally still be scoping problems with that approach.
In a browser, each distinct
documentobject is its own new global scope, and in browser JS it is possible to create new documents (such as those used for off-screen rendering or to create document fragments) or to get a reference to another page’sdocumentobject (such as via inter-page communication using a named-target link) so it’s possible in certain (rare?) circumstances that object prototypes won’t have the methods you expect them to have—though you could always run your polyfills again against the new global objects…In Node.js, modifying prototypes of
globalobjects may be safe, but modifying the prototypes of non-global, imported objects could lead to breakage if you ever end up with two versions of the same package being required/imported, because imports of the two versions will not expose the same objects, thus won’t have the same object prototypes. That is, your code could work fine until a dependency or sub-dependency uses a different version from the one you expect, and without any of your own code changing, a simplenpm installoryarn installcould trigger this problem. (There are options to deal with this, such as yarn’sresolutionsproperty in thepackage.json, but that’s not a good thing to rely on if you have other options.)