简体   繁体   中英

Pass `Array.prototype.includes` to callback without wrapping it in an anonomous function?

Why do I need to wrap animals.includes in an anonymous function for the following ES6 js code to work as expected?

const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]

const hasPulse = nouns.some((n) => animals.includes(n))

console.log(`Heartbeat ${hasPulse ? '' : 'not '}detected!`)

If I unwrap animals.includes it throws a type error.

// this fails with TypeError: can't convert undefined to object
const hasPulse = nouns.some(animals.includes)

You could not take

const hasPulse = nouns.some(Array.prototype.includes, animals);

with thisArgs , but unfortunately Array#includes features a second parameter fromIndex , which is handed over by the index and destroys the wanted result.

I believe this is a context issue, you should bind the includes function to the nouns array for it to work the way you want it to. The reason why the code you have is not working as you expect it is because when you pass the includes function to the some, it is not executed on the array (this is either undefined or is set to the window object in the browser). Here is the working example

 const animals = ["ape", "dog", "pig"] const nouns = ["car", "planet", "apple", "dog"] const hasPulse = nouns.some(animals.includes.bind(nouns)) console.log(`Heartbeat ${hasPulse? '': 'not '}detected!`)

It has to do with how a Javascript method "knows" which object it was attached to when called: its this reference.

includes might be implemented something like this (hypothetical example):

Array.prototype.includes = function includes(couldBeMember) {
  for (const member of this) {
    if (member === couldBeMember) {
      return true;
    }
  }
  return false;
}

animals.includes(argument) passes argument to Array.prototype.includes , but also sets its this to animals during the call. Just referencing animals.includes and passing that elsewhere, is no different than passing Array.prototype.includes ; if it is ultimately called (inside some ) without reference to animals , it won't get animals set as its this .

To create a function includes that "remembers" its this is animals , no matter where the function by itself is passed, you have to bind it to animals : animals.includes.bind(animals) (or Array.prototype.includes.bind(animals) or [].includes.bind(animals) ).

const animals = ["ape", "dog", "pig"]
const nouns = ["car", "planet", "apple", "dog"]

const hasPulse = nouns.some([].includes.bind(animals))

This isn't a whole lot cleaner than just using a lambda, but hopefully answers your question.

If you like the idea but want to clean it up, you could create a freestanding bind that does something like so:

function bind(self, method, ...args) {
  return self[method].bind(self, ...args);
}

const hasPulse = nouns.some(bind(animals, "includes"))

(There's also at least one proposal out there to create an operator that performs the equivalent binding, something like animals::includes , but I don't believe that's finalized yet last I checked so the syntax might change or it might not end up supported.)

It's because of the incompatibility between how Array.prototype.some() calls it's callback and how Array.prototype.includes() expect arguments.

The full definition of [].includes() is:

[].includes(thingToSearch, startSearchFrom);

For example, if you do:

[1,2,3,4,5].includes(1,3); // false

It would return false even though the array clearly includes 1 . This is because you told it to start searching from element 3 so .includes() only looked at 4 and 5 and could not find 1 .

The full definition of [].some() is:

[].some(function (item, index, theArray) {}, this);

So the .some() will pass the index of the current item to the callback you pass to it. For example if you do:

[10,20,30,40,50].some((x,y) => y == 2); // 30

It would return 30 because he first time .some() loops over the array it passes 10 to x and 0 to y . The second time it passes 20 to x and 1 to y . And the third time y will be 2 so y == 2 is true so it returns the value where it is true which is 30 .

Now you can see that what .some() passes as the second argument and what .includes() expects as the second argument have different meanings.

The .some() method will pass the loop index while the .includes() method expect the second argument to tell it where to start searching.

So if you do:

const animals = ["ape", "dog", "pig"];
const nouns = ["car", "planet", "apple", "dog"];

nouns.some(animals.includes);
  1. .. it would search for car in ape,dog,pig ,

  2. .. then it would search for planet in dog,pig (remember index is now 1 so you are telling .includes() to ignore ape )

  3. .. then it would search for apple in pig (index is now 2 so .includes() ignores ape and dog )

  4. .. then it would search for dog in an empty array which of course it would not find.

Sure, there may be some algorithm where you want to do this. But I don't think this is what you expect to happen.

The some() method tests if one element or more in the array passes the test implemented in the provided function and returns a Boolean value indicating the result.

const hasPulse = nouns.some((n) => animals.includes(n))

The second alternative won't work. Array.includes uses a second parameter (fromIndex) which is received from the calling function as index and omits values to be checked by that function.

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