Array.prototype.includes vs. Array.prototype.indexOf
tl;dr: NaN
is treated differently:
[NaN].indexOf(NaN) > -1
isfalse
[NaN].includes(NaN)
istrue
From the proposal:
Motivation
When using ECMAScript arrays, it is commonly desired to determine if the array includes an element. The prevailing pattern for this is
if (arr.indexOf(el) !== -1) {
...
}
with various other possibilities, e.g.
arr.indexOf(el) >= 0
, or even~arr.indexOf(el)
.These patterns exhibit two problems:
- They fail to "say what you mean": instead of asking about whether the array includes an element, you ask what the index of the first occurrence of that element in the array is, and then compare it or bit-twiddle it, to determine the answer to your actual question.
- They fail for
NaN
, asindexOf
uses Strict Equality Comparison and thus[NaN].indexOf(NaN) === -1
.
Proposed Solution
We propose the addition of an
Array.prototype.includes
method, such that the above patterns can be rewritten asif (arr.includes(el)) {
...
}
This has almost the same semantics as the above, except that it uses the SameValueZero comparison algorithm instead of Strict Equality Comparison, thus making
[NaN].includes(NaN)
true.Thus, this proposal solves both problems seen in existing code.
We additionally add a
fromIndex
parameter, similar toArray.prototype.indexOf
andString.prototype.includes
, for consistency.
Further information:
SameValueZero
algorithmStrict Equality Comparison
algorithm
Array indexOf() vs includes() perfomance depending on browser and needle position
I made a test using array with 10 000 numeric values, here is results:
Chrome:
- beginning
- includes (22,043,904 ops/sec)
- indexOf (136,512,737 ops/sec)
- middle
- includes (8,361 ops/sec)
- indexOf (31,296 ops/sec)
- ending
- includes (4,018 ops/sec)
- indexOf (95,221 ops/sec)
Firefox:
- beginning
- includes (34,087,623 ops/sec)
- indexOf (33,196,839 ops/sec)
- middle
- includes (84,880 ops/sec)
- indexOf (86,612 ops/sec)
- ending
- includes (25,253 ops/sec)
- indexOf (14,994 ops/sec)
So, indexOf()
in Chrome works much faster than includes()
in all positions.
In Firefox both indexOf()
and includes()
works almost similar.
Why do includes() and indexOf() work differently with searching NaN?
Basically, Array.prototype.indexOf
uses a different equality algorithm than Array.prototype.includes
.
Array.prototype.indexOf
ends up using Number::equal
. Which does:
- If x is NaN, return false.
- If y is NaN, return false.
While Array.prototype.includes
ends up using Number::sameValueZero
Which does:
- If x is NaN and y is NaN, return true.
As to why does the spec allow a comparison of two different NaNs to be true, well that's up to the TC. I can only guess they made it this way as it's a convenient way to check if there's a NaN
in the array.
JavaScript indexOf vs Array.prototype.indexOf IE compatibility error
This question has come up many times, another Stackoverflow thread can be found here: How to fix Array indexOf() in JavaScript for Internet Explorer browsers
Several people on different threads have recommended using this code from MDC. This appears to be the code:
if (!Array.prototype.indexOf)
{
Array.prototype.indexOf = function(elt /*, from*/)
{
var len = this.length >>> 0;
var from = Number(arguments[1]) || 0;
from = (from < 0)
? Math.ceil(from)
: Math.floor(from);
if (from < 0)
from += len;
for (; from < len; from++)
{
if (from in this &&
this[from] === elt)
return from;
}
return -1;
};
}
Just run this code before making any calls indexOf()
Array.prototype.includes() wildcard options?
As others mentioned, you cannot use regex with Array.includes()
. The simplest way would be to use a some()
or every()
(depending on what do you want to achieve) instead:
var ReviewtagFilter = function() {
let reviewsTagged = [];
let tagLevel = "_easy";
let tagTopic = /^-/;
reviewsTagged = cards.filter(
data => data.some(tag => tag.match(tagTopic)) && data.tags.includes(tagLevel)
);
console.log(reviewsTagged)
};
Bug in Array.prototype.includes?
It's not a bug in includes
. :-)
The problem is that includes
accepts an optional second parameter, which is the index at which to start searching, and some
provides three arguments to its callback: The item, its index, and the object being searched.
So with
[0, 1].some(Array.prototype.includes, [1])
These calls are made (effectively):
[1].includes(0, 0)
- false, the array doesn't contain0
[1].includes(1, 1)
- false, the array contains1
at index0
, but the search starts at index1
This comes up periodically. For instance, when trying to use parseInt
as a callback directly to convert an array of strings into an array of numbers, because of parseInt
's second parameter (the number base, or radix, to use):
console.log(["6", "9", "7"].map(parseInt));
How do I check if an array includes a value in JavaScript?
Modern browsers have Array#includes
, which does exactly that and is widely supported by everyone except IE:
console.log(['joe', 'jane', 'mary'].includes('jane')); //true
Related Topics
Is There a JavaScript MVC (Micro-)Framework
Copy a Variable's Value into Another
Get Position/Offset of Element Relative to a Parent Container
Jquery Clone Form Fields and Increment Id
Why Are Certain Function Calls Termed "Illegal Invocations" in JavaScript
Moment.Js Transform to Date Object
Getting a Jquery Selector for an Element
Baking Transforms into Svg Path Element Commands
Example of a Circular Reference in JavaScript
Script Tag in Angular2 Template/Hook When Template Dom Is Loaded
Jquery's Live() Is Deprecated. What Do I Use Now
How to Ungzip (Decompress) a Nodejs Request's Module Gzip Response Body
How to Set a Cookie with Expire Time
Convert Nan to 0 in JavaScript