0

I would expect [1].some([1].includes) to return true, instead I get an error saying:

Uncaught TypeError: Cannot convert undefined or null to object.

Any ideas what would cause this? As I understand, Array.some accepts a function invoked per array item, which [1].includes should fulfill.

Elliot
  • 1,893
  • 4
  • 17
  • 35
  • 1
    _“This question has nothing to do with `this`”_ — It has _everything_ to do with `this`. Just because _you_ didn’t type `this` yourself, it doesn’t mean it’s not there. It _is_ there; see the [specification for `Array.prototype.includes`](//tc39.es/ecma262/#sec-array.prototype.includes): Step 1: “Let _O_ be ? ToObject(**this** value).”. It’s used right there in the first step. The linked Q&A and [How does the "this" keyword work, and when should it be used?](/q/3127429/4642212) explain how passing `[1].includes` as a parameter loses the base context from which the `this` value is derived. – Sebastian Simon Jan 31 '23 at 16:00
  • You are right, thanks for clarifying; I misunderstood. Edited that content out. – Elliot Feb 01 '23 at 18:29

2 Answers2

4

You lose the context when you pass that function as is: when it's invoked as a callback, value of this inside it is undefined in strict mode (and global object in non-strict mode), not [1]. To address this issue, you may fix the context:

[1].some([].includes.bind([1]))

Note that it doesn't matter which array is used to access includes function; you might as well write that as...

[1].some( Array.prototype.includes.bind([1]) )

That's be a bit less concise, but a bit more efficient (as no immediate array is created). Still, it almost never should be a bottleneck; thus you should better optimize for readability.


Unfortunately, this won't be enough. See, Array.includes() uses two parameters:

arr.includes(searchElement[, fromIndex])

... and Array.some() does supply it with those two (even three in fact, but only two are used by includes). That's why this...

[1,2,3].some([].includes.bind([1])); // true

... works, but this...

[2,1,3].some([].includes.bind([1])); // false

... doesn't: the lookups in [1] array start from 0th, 1st, 2nd elements - and apparently fail after the first one.

To fix this, you might either create a function that takes exactly one argument with something like lodash's _.unary:

[2,1,3].some(_.unary([].includes.bind([1]))) // now we're talking!

... or bite the bullet and use arrow function instead. Note that you can still use a function with a bound context here:

const checker = [].includes.bind([1]);
[2,1,3].some(el => checker(el));

... to make this more flexible.

raina77ow
  • 103,633
  • 15
  • 192
  • 229
  • Great answer. I was unaware a second argument could be provided to Array#includes. Probably a good illustration of why using variable arity functions point-free is a bit of a footgun. Having thought about this, my preference would be for the lambda function now, for clarity if nothing else. – Alex Young Jul 10 '18 at 22:50
2

It depends on the specific implementation of includes in whatever JS engine you are using, but usually the standard library functions are not very co-operative for point-free style programming.

Generally this is because the context (this) is not assigned as intended. This can be shown by trying the following:

[1].some([1].includes); // Error
[1].some([1].includes.bind([1])) // true

Edit: This answer is not entirely correct. You should probably read rain77ow's answer above

Alex Young
  • 4,009
  • 1
  • 16
  • 34