2

I encountered strange behavior of Array.prototype.includes in one edge case.

Given that Array.prototype.includes works on bound context, one might use it like this (which is working)

expect(Array.prototype.includes.call([1, 2], 1))).toBe(true)

simply put, we bound array [1, 2] and test 1 for inclusion.

Then consider, that many Array.prototype methods are able to bound context to provided callback, so for example Array.prototype.some can be combined with Object.prototype.hasOwnProperty like this

expect(["foo", "bar"].some(Object.prototype.hasOwnProperty, { foo: 0 })).toBe(true)

Here, .some accepts two parameters, (callback, [thisArg]), where optional thisArg, when provided, is bound to callback, thus previous example binds { foo: 0 } to callback Object.prototype.hasOwnProperty and then tests all items in ["foo", "bar"] if at least one is own property of { foo: 0 }. This example is also working.

But something strange happen, if you try to use Array.prototype.includes as callback.

[0, 1].some(Array.prototype.includes, [1]) // => false

here we bind array [1] to Array.prototype.includes and we test every item of [0, 1] if at least one is included. But this case returns false, which is against our expectation.

Strangely, if bound array contains other number than 1 or contains more than one item, the test passes

[0, 1].some(Array.prototype.includes, [0]) // => true
[0, 1].some(Array.prototype.includes, [1, 1]) // => true
// but
[0, 1].some(Array.prototype.includes, [1]) // => false

It seems like array [1] is handled improperly.

Tested in Node v.11.11.0 Node v.8.11.3 and Chrome 73

I tested basically just V8 engine. Can anyone report output in Chakra?

1 Answer 1

15

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 contain 0
  • [1].includes(1, 1) - false, the array contains 1 at index 0, but the search starts at index 1

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));

The 9 and 7 fail becaue the calls are (effectively):

  • parseInt("6", 0) - works because parseInt ignores the invalid radix 0.
  • parseInt("9", 1) - NaN because parseInt always returns NaN for radix 1
  • parseInt("7", 2) - NaN because "7" is not a valid digit in base 2 (binary)

The moral of the story: Remember the not-commonly-used arguments that map, some, forEach, and various other methods provide to callbacks. :-)


In one codebase I was working in, they had a clamp function that accepted a function and ensured that, regardless of how many arguments it was called with, it would only pass on the desired number of arguments. If you were using includes like this a lot, you could create a clamped includes:

function clamped(fn, count) {
    return function(...args) {
        return fn.apply(this, args.slice(0, count));
    }
}

const includes = clamped(Array.prototype.includes, 1);

console.log([0, 1].some(includes, [1])); // true
console.log([0, 1].some(includes, [3])); // false

The handy thing about that is that that includes is reusable.

Or of course, just use a wrapper function:

console.log([0, 1].some(function(entry) {
    return this.includes(entry);
}, [1])); // true
console.log([0, 1].some(function(entry) {
    return this.includes(entry);
}, [3])); // false


These are all meant to be general solutions, of course. If you specifically want to know if array a contains any of the entries in array b, there are more specific implementations you can build to handle that efficiently depending on the characteristics of a and b.

Sign up to request clarification or add additional context in comments.

3 Comments

Brilliant. I'm aware of this problem, but I did not knew that includes has arity of 2 and I did not suspect this be a problem. I will use Ramdas utility function unary to handle this scenario. Thanks for explanation.
@T.J. Crowder: Did you mean, "just use an anonymous function"?
@justDev7a3 - LOL I certainly didn't mean "arrow function." :-) Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.