Before includes existed, every presence check went through indexOf and a comparison against -1. The method stayed, because it answers a different question: not whether the value is there, but where it is.
And that -1, picked as the sign of absence, is worth a stop.
Definition
indexOf hands back the index of the first occurrence of a value, or -1 when it is not there. It works on an Array as well as on a String.
const languages = ["fr", "en", "es"];
console.log(languages.indexOf("en")); // 1
console.log(languages.indexOf("de")); // -1
const path = "/en/courses/formation-javascript";
console.log(path.indexOf("/courses")); // 3
console.log(path.slice(path.indexOf("formation-"))); // "formation-javascript"That last line shows the use that still justifies the method: cutting a string at a landmark, which includes cannot do since it only hands back a boolean.
The zero trap
A value found in first position hands back 0, which is a falsy value under the Truthy and falsy rules. Testing the result as a condition therefore flips the logic.
const roles = ["ROLE_ADMIN", "ROLE_USER"];
if (roles.indexOf("ROLE_ADMIN")) {
console.log("never printed"); // the index is 0, so the condition is false
}
if (roles.indexOf("ROLE_ADMIN") !== -1) {
console.log("correct");
}The explicit comparison against -1 is not optional. When presence is all you care about, includes() removes the problem at the root by handing back a boolean directly.
The three search methods
| Method | What it looks for | What it hands back |
|---|---|---|
indexOf(value) | An exact value | An index, or -1 |
lastIndexOf(value) | The same, starting from the end | An index, or -1 |
findIndex(function) | The first item passing a test | An index, or -1 |
The three columns read together: the method, what it takes, what it hands back. The comparison in the first two follows Strict equality (===), so an object is never found by its content. That is what findIndex is for, since it receives a function and compares whatever you want.
Frequently asked questions
Why -1 rather than undefined?
Because the method always hands back a number, and -1 is the only impossible index in an array. The choice dates back to the earliest versions of the language and never changed, for compatibility reasons. Newer methods such as Array.find() do hand back undefined.
How do you find every occurrence?
By restarting the search after each hit, thanks to the second argument that sets the starting point: let i = text.indexOf("a"); while (i !== -1) { positions.push(i); i = text.indexOf("a", i + 1); }. Forgetting the + 1 produces an infinite loop, since the same position is found on every turn.
Why does indexOf miss NaN?
Because it compares with ===, and NaN === NaN is false by definition. An array holding NaN therefore hands back -1. includes() uses a slightly different comparison and does find it, which is one of the rare cases where the two methods disagree.