Today I was surprised to learn that the behavior for _.indexOf
is not equivalent to indexOf
on Strings.
> \_.indexOf("abc", "bc")
> < -1
> "abc".indexOf("bc")
> < 1
>
What?
Before I explain, let’s note that there are two distinct indexOf
functions, one for Strings and one for Arrays. Interestingly, the two have differing browser support for IE — String.prototype.indexOf
has IE6+ support while Array.prototype.indexOf
has IE9+ support.
_.indexOf
is for arrays. Note that it is under the “Array” heading and the documentation suggests that it takes an array, not a string. Unfortunately, the intent of _.indexOf is obfuscated when stumbling upon its usage in a codebase.
Of course, this subtlety is more misleading if you were to think “Strings are just arrays of characters!” and not appreciate the differences in implementation of the String.prototype.indexOf
and Array.prototype.indexOf
. String’s indexOf
performs substring operations while Array’s cannot.
To illustrate the point, consider the differences between "abc".indexOf("bc")
and ["a","b","c"].indexOf(["b", "c"])
. The former returns 1
and the latter -1
.
Reading the _.indexOf
’s implementation we can walk through what happens when we try to run _.indexOf("abc", "bc")
. Underscore first attempts to delegate to Array.prototype.indexOf
, which fails because "abc".indexOf === String.prototype.indexOf !=== Array.prototype.indexOf
. Next, Underscore.js loops through the string, one character at a time, and compares it and the item.
"a" !== "ab"
"b" !== "ab"
"c" !== "ab"
Finally, we return false
.
Confusingly, _.indexOf
actually works for strings iff the item is a single character (e.g. _.indexOf("abc", "c")
returns 2
).
Lessons learned: If you use _.indexOf
, please know that it is only intended for usage when Array.prototype.indexOf
on the parameters is appropriate. If you want Array.prototype.indexOf for pre-IE9 browsers and you also have Underscore.js, use _.indexOf
. If you need to do substring operation, use String.prototype.indexOf
.