简体   繁体   中英

Bug in Array.prototype.includes?

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?

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM