204

I'm checking a variable, say foo, for equality to a number of values. For example,

if( foo == 1 || foo == 3 || foo == 12 ) {
    // ...
}

The point is that it is rather much code for such a trivial task. I came up with the following:

if( foo in {1: 1, 3: 1, 12: 1} ) {
    // ...
}

but also this does not completely appeal to me, because I have to give redundant values to the items in the object.

Does anyone know a decent way of doing an equality check against multiple values?

pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • I think the larger context needs to be involved when deciding something like this, because it's important to know *why* you're making such comparisons. – Pointy Jan 18 '11 at 19:30
  • Well, in a game I'm creating I'm checking keyboard codes so as to decide what function should be called. In different browsers, a key has different key codes appearently, hence the need to compare with multiple values. – pimvdb Jan 18 '11 at 19:32
  • 1
    check performance test for multiple methods, logical operator wins https://runkit.com/pramendra/58cad911146c1c00147f8d8d – Pramendra Gupta Mar 16 '17 at 19:04
  • 1
    This is a common duplicate target for questions asking why `variable === value1 || value2` isn’t working. For those wondering _why_: `||` and `&&` operate with [short-circuit evaluation](/q/4535647/4642212); each operand is either [truthy or falsy](/q/19839952/4642212). `x === 1 || 2` means `(x === 1) || (2)` and is either `true` or `2`, since `||` results in the first truthy or the last falsy operand, and `x === "a" || "b"` is either `true` or `"b"`. Since `true`, `2`, and `"b"` are all truthy, putting them in an `if` condition is equivalent to `if(true)`. Just use `[ "a", "b" ].includes(x)`. – Sebastian Simon Aug 24 '21 at 17:56
  • See [Why my condition is always true](/q/49735149/4642212). – Sebastian Simon Nov 08 '22 at 15:40

16 Answers16

234

In ECMA2016 you can use the includes method. It's the cleanest way I've seen. (Supported by all major browsers)

if([1,3,12].includes(foo)) {
    // ...
}
Alister
  • 27,049
  • 9
  • 40
  • 35
218

You could use an array and indexOf:

if ([1,3,12].indexOf(foo) > -1)
Gumbo
  • 643,351
  • 109
  • 780
  • 844
  • 1
    I like this one. It would even be possible to create a 'contains' function through prototype I guess, so as to eliminate the use of > -1. – pimvdb Jan 18 '11 at 19:30
  • 1
    @pimvdb: Note that you might need your own implementation as `indexOf` is only available since ECMAScript 5th edition. – Gumbo Jan 18 '11 at 19:34
  • Is '>' somehow better than '!=='? – Max Waterman May 25 '20 at 03:44
  • 3
    You can use `~` operator instead of `> -1`: `if ( ~[...].indexOf(foo) ) { ... }` – Vahid Nov 21 '20 at 08:22
  • @MaxWaterman first of all, there is absolutely no point of checking type of the result since `indexOf` always returns a number, so `!=` would suffice. And `>` is shorter, so yes, it's better – vanowm Jan 05 '22 at 12:30
  • @vanowm You imply that '!==' is more work than '!=' but surely it is *less* work (ie something like x == -1" || x == '-1' in the underlying engine)? Also, shorter is NOT better -clear meaning is better, and the intended meaning should be 'is not -1', so why not say that in the code? – Max Waterman Jan 07 '22 at 01:14
  • @MaxWaterman and how is that checking 2 conditions is less work than checking 1 condition? You've lost me on that one. Clearly the whole example was meant to be shorter, not clearer, otherwise the array would be in a separate variable. Anyhow, to each it's own, I find it's easier read with `>` operator than `!==` – vanowm Jan 07 '22 at 04:08
  • It isn't less work, but that's my point. It's 'are they the same type?compare their values:false' vs 'convert to the same type; compare values'. I suggest the former is quicker because 'convert to the same type' is not simple. re '-1' is obviously a different meaning to every other return value, so it should be treated as such. It's essentially a hangover from C libraries, presumably. Yeah, whatever...it's common enough, esp if you come from the 'C' world. – Max Waterman Jan 08 '22 at 05:39
10

Using the answers provided, I ended up with the following:

Object.prototype.in = function() {
    for(var i=0; i<arguments.length; i++)
       if(arguments[i] == this) return true;
    return false;
}

It can be called like:

if(foo.in(1, 3, 12)) {
    // ...
}

Edit: I came across this 'trick' lately which is useful if the values are strings and do not contain special characters. For special characters is becomes ugly due to escaping and is also more error-prone due to that.

/foo|bar|something/.test(str);

To be more precise, this will check the exact string, but then again is more complicated for a simple equality test:

/^(foo|bar|something)$/.test(str);
pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • 8
    Goodness! I'm astounded that works — `in` is a keyword in Javascript. For example, running `function in() {}; in()` results in a syntax error, at least in Firefox, and I'm not sure why your code doesn't. :-) – Ben Blank Feb 04 '11 at 18:35
  • 10
    Also, it's often considered bad practice to extend `Object.prototype`, as it affects `for (foo in bar)` in unfortunate ways. Perhaps change it to `function contains(obj) { for (var i = 1; i < arguments.length; i++) if (arguments[i] === obj) return true; return false; }` and pass the the object as an argument? – Ben Blank Feb 04 '11 at 18:38
  • Thanks for your reaction, and the Google Closure Compiler also returns an error whilst compiling while this code itself works beatifully. Anyway, it has been suggested that a helper class can do this job better than extending `Object.prototype` as you also mention. However, I do prefer this way, as the notation for checking is lovely, just `foo.in(1, 2, "test", Infinity)`, easy as pie. – pimvdb Feb 04 '11 at 19:44
  • And defining a function `in()` the regular way fails on Chrome too, but prototype works... :) – pimvdb Feb 04 '11 at 19:45
  • Although contains reads the wrong way around "x.contains(list)" you could call it within: "x.within(list)" which reads the same way as the original. – Andrew May 15 '15 at 10:31
  • 7
    Extending the Object.prototype is an anti-pattern and shouldn't be used. Use a function instead. – Vernon Dec 14 '16 at 10:11
  • 2
    As mentioned above extending `Object.prototype` this way has serious disadvantages and can lead to many bugs. If you really do want to extend `Object.prototype` always do it via `Object.defineProperty` without setting `enumerable` in descriptor. – Pinke Helga Mar 07 '19 at 00:59
  • 2
    @BenBlank - Just FYI, using a keyword as a literal property name became valid in the 5th edition spec (Dec 2009), which is why `x.in()` works. Prior to the 5th edition you would have had to use a computed property name (`x["in"]()`). – T.J. Crowder Nov 18 '20 at 14:51
  • Both approaches are really bad; **please never use either**! The [`Object.prototype` approach](/q/14034180/4642212) creates an [enumerable property without checking its existence first](/a/46491279/4642212), leading to the aforementioned possible enumeration bugs (`for`–`in`) and to serious web compatibility issues as it severely limits the possibility to extend the language specification with methods in the future due to name collisions. The first regex doesn’t check exact strings, so why even include it? For this, regex quickly becomes unmaintainable. Just use `.includes` or `Set`’s `.has`. – Sebastian Simon Mar 23 '21 at 22:24
9

Now you may have a better solution to resolve this scenario, but other way which i preferred.

const arr = [1,3,12]
if( arr.includes(foo)) { // it will return true if you `foo` is one of array values else false
  // code here    
}

I preferred above solution over the indexOf check where you need to check index as well.

includes docs

if ( arr.indexOf( foo ) !== -1 ) { }
Javed Shaikh
  • 663
  • 7
  • 16
7

This is easily extendable and readable:

switch (foo) {
    case 1:
    case 3:
    case 12:
        // ...
        break

    case 4:
    case 5:
    case 6:
        // something else
        break
}

But not necessarily easier :)

orlp
  • 112,504
  • 36
  • 218
  • 315
  • I actually used this method as I wanted to put a comment next to each value to describe what the value related to, this was the most readable way to achieve it. – pholcroft Jun 15 '18 at 08:46
  • @pholcroft if you need to describe what each value relates to you should create an enum, or class, and write your properties to have descriptive names. – red_dorian Jun 22 '18 at 10:57
7

You can write if(foo in L(10,20,30)) if you define L to be

var L = function()
{
    var obj = {};
    for(var i=0; i<arguments.length; i++)
        obj[arguments[i]] = null;

    return obj;
};
Elian Ebbing
  • 18,779
  • 5
  • 48
  • 56
  • 3
    I think it is prudent to remark that prototype functions are also 'in' an array/object - so if there is a function 'remove' in an array/object's prototype, it will always return true if you code `'remove' in L(1, 3, 12)`, although you didn't specify 'remove' to be put in the list. – pimvdb Jan 20 '11 at 10:19
6
var a = [1,2,3];

if ( a.indexOf( 1 ) !== -1 ) { }

Note that indexOf is not in the core ECMAScript. You'll need to have a snippet for IE and possibly other browsers that dont support Array.prototype.indexOf.

if (!Array.prototype.indexOf)
{
  Array.prototype.indexOf = function(searchElement /*, fromIndex */)
  {
    "use strict";

    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (len === 0)
      return -1;

    var n = 0;
    if (arguments.length > 0)
    {
      n = Number(arguments[1]);
      if (n !== n)
        n = 0;
      else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0))
        n = (n > 0 || -1) * Math.floor(Math.abs(n));
    }

    if (n >= len)
      return -1;

    var k = n >= 0
          ? n
          : Math.max(len - Math.abs(n), 0);

    for (; k < len; k++)
    {
      if (k in t && t[k] === searchElement)
        return k;
    }
    return -1;
  };
}
meder omuraliev
  • 183,342
  • 71
  • 393
  • 434
  • It is Mozilla's copy directly. Was too lazy to link. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf – meder omuraliev Jan 18 '11 at 19:57
  • Is there a reason for the bitshift when you define len if you aren't actually shifting bits? Is there some implicit coercion going on here? – jaredad7 Sep 04 '18 at 15:52
  • *"Note that indexOf is not in the core ECMAScript."* It was added in the 5th edition specification, Dec 2009. – T.J. Crowder Nov 18 '20 at 14:47
4

Also, since the values against which you're checking the result are all unique you can use the Set.prototype.has() as well.

var valid = [1, 3, 12];
var goodFoo = 3;
var badFoo = 55;

// Test
console.log( new Set(valid).has(goodFoo) );
console.log( new Set(valid).has(badFoo) );
Ivan Sivak
  • 7,178
  • 3
  • 36
  • 42
2

For a larger list of values that you compare with often, it is likely more optimal to first construct a Set and use Set#has for a constant time check rather than Array#includes which runs in linear time.

const values = new Set([1, 2, 12]);
if(values.has(foo)){
   // do something
}
Unmitigated
  • 76,500
  • 11
  • 62
  • 80
1

I liked the accepted answer, but thought it would be neat to enable it to take arrays as well, so I expanded it to this:

Object.prototype.isin = function() {
    for(var i = arguments.length; i--;) {
        var a = arguments[i];
        if(a.constructor === Array) {
            for(var j = a.length; j--;)
                if(a[j] == this) return true;
        }
        else if(a == this) return true;
    }
    return false;
}

var lucky = 7,
    more = [7, 11, 42];
lucky.isin(2, 3, 5, 8, more) //true

You can remove type coercion by changing == to ===.

Greg Perham
  • 1,835
  • 1
  • 17
  • 26
1

If you have access to Underscore, you can use the following:

if (_.contains([1, 3, 12], foo)) {
  // ...
}

contains used to work in Lodash as well (prior to V4), now you have to use includes

if (_.includes([1, 3, 12], foo)) {
  handleYellowFruit();
}
David Salamon
  • 2,361
  • 25
  • 30
0

I simply used jQuery inArray function and an array of values to accomplish this:

myArr = ["A", "B", "C", "C-"];

var test = $.inArray("C", myArr)  
// returns index of 2 returns -1 if not in array

if(test >= 0) // true
ScottyG
  • 3,204
  • 3
  • 32
  • 42
0

This is a little helper arrow function:

const letters = ['A', 'B', 'C', 'D'];

function checkInList(arr, val) {
  return arr.some(arrVal => val === arrVal);
}

checkInList(letters, 'E');   // false
checkInList(letters, 'A');   // true

More info here...

northernwind
  • 628
  • 7
  • 14
  • Very good approach: starting from your idea you can also do it without a helper funcion: `const valToCheck = 'E'; const check = ['A', 'B', 'C', 'D'].some(option => option === valToCheck); // false` – Giorgio Tempesta Sep 18 '19 at 08:41
  • But once I've started tweaking the code I have found that `includes` becomes the better option, as in @alister's answer: `const valToCheck = 'E'; const check = ['A', 'B', 'C', 'D'].includes(valToCheck); // false` – Giorgio Tempesta Sep 18 '19 at 08:51
0

It really comes down the the use case. For example, if you only care if the variable is of any specific value, using Array.some() would be the best bet as it basically performs a forEach on any iterable type and returns true at the first value the condition meets. This makes it one of the fastest ways to find out if any option meets the condition and this is an example of how it's used.

const anyOfThese = [1, 2, 14];
const doesHave0 = 0;
const doesHave1 = 1;
const doesHave14 = 14;

// checks all
const found0 = anyOfThese.some(singleValue => singleValue === doesHave0));
console.log("found0", found0);
// -> false

// check anyOfThese[0] = (1) returns without checking the rest
const found1 = anyOfThese.some(singleValue => singleValue === doesHave1));
console.log("found1", found1);
// -> true

// checks anyOfThese[0...14] until it finds singleValue === 14
const found14 = anyOfThese.some(singleValue => singleValue === doesHave14));
console.log("found14", found14);
// -> true

However, if you're storing values in some set that you want to track the total result of and you're only adding items if the set doesn't already have them for example. Using an actual Set would be better because Sets can only have one entry for each unique value and simply adding it to the set like such.

const mSet = new Set();
mSet.add(2);
mSet.add(5);
mSet.add(2);
console.debug("mSet", mSet); // -> mSet Set {2, 5}

and if you need to know wether or not the value was added you can simply compare the size of the Set inline with the add like this

const mSet = new Set();
mSet.add(2);
const addedFive = mSet.size < mSet.add(5).size;
console.debug("addedFive", addedFive);  // -> true
const addedTwo = mSet.size < mSet.add(2).size;
console.debug("addedTwo", addedTwo);  // -> false
console.debug("mSet", mSet); // -> Set {2, 5}

And that < can be any logical check, so you could say

const mSet = new Set([2]);
mSet.size === mSet.add(2); // -> returns true; didn't add 2
mSet.size !== mSet.add(2); // -> returns false; didn't add 2
mSet.size === mSet.add(5); // -> returns false; added 5
mSet.size !== mSet.add(6); // -> returns true; added 6

Or you could also use any function, so you could also say

const mSet = new Set([2]);
mSet.size === mSet.remove(2); // -> returns false; removed 2
mSet.size === mSet.remove(2); // -> returns true; didn't remove anything

The < and > operators make it easier to clarify what you're checking for but === and !== provide a broader range of possibilities in cases where the exact change may not be important, but rather if anything did or did not change.

Just remember when writing your logic that these checks mutate your values, so that Set will really have the checked value added or removed.

AriesNinja
  • 55
  • 10
theZ3r0CooL
  • 147
  • 2
  • 10
-1

Using the ES6 syntax you can do:

Object.prototype.in = function (...args) {
  return args.includes(this.valueOf());
}

which incidentally also checks for deep equality. The valueOf() method is needed as this is an object.

melv douc
  • 17
  • 2
-6

For posterity you might want to use regular expressions as an alternative. Pretty good browser support as well (ref. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#Browser_compatibility)

Try this

if (foo.toString().match(/^(1|3|12)$/)) {
    document.write('Regex me IN<br>');
} else {
    document.write('Regex me OUT<br>');
}
martisj
  • 171
  • 3
  • 9