Difference between findIndex() and some() in JavaScript - javascript

Is the difference just that while some() returns true if there's at least element that passes the test in the callback function, findIndex() returns index of the first element that succeeds the test?
Or is one more optimal or is there more to it than I understand, because, if that's the only difference then I could simply get the index from findIndex() and check if it's -1 or not.
let foo = [
{
'animal':'dog'
},
{
'animal':'cat'
},
{
'animal':'cow'
},
]
foo.find( obj => obj.animal === 'cat' )
{animal: "cat"}
foo.some( obj => obj.animal === 'cat' )
true

findIndex and some are indeed very similar and you can easily use findIndex instead of some. Also, find has a similar specification. But you cannot easily use it to replace some as the return value of find is the found element or undefined which you cannot tell apart from an actual element.
There is, however, a small difference between findIndex/find and some. See the specification of some:
callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.
See this example:
new Array(1).findIndex(() => console.log('called')); // prints 'called' once
new Array(1).some(() => console.log('called')); // does not print anything
That should not actually be relevant in most applications, though.
The reason why those functions exist and you should use them how they were specified is readability. You should always strive to write readable/maintanable code. Of course you can write [].findIndex(...) !== -1 instead of [].some(...) but the latter is clearly more readable as it has better semantics of what it does.

Related

I really don't understand this if with NOT! operator

firstly here is my code :
const removeFromArray = function (...args) {
const array = args[0];
const newArray = [];
array.forEach((item) => {
if (!args.includes(item)) {
newArray.push(item);
}
});
return newArray;
The problem is this !args.includes(item), the NOT operator, it really makes no sense in my mind in this case... I tried to reformulate, rewrite but it doesn't help.
The function is supposed to push every element into the array unless it is included in the function arguments (for example with arguments like this : removeFromArray([1, 2, 3], 2, 4)) but how the 'unless' work with this if condition ?
Can you light up my lantern please ?
It seems you kind of interpreting the "unless" as literal here and I see how this might be a little bit confusing at first.
Perhaps if it was rephrased, it might be less confusing:
don't push element into array unless it's NOT including in the arguments.
Or more literal to actual code:
if item is NOT in the arguments, add it to the array
.includes() returns true or false and ! negates the result. The loop will only execute if .includes() in your example returns false.
To me it seems as if you want to filter the array, so maybe you should use the .filter() method:
const newArray = array.filter((el)=>!(testArray.includes(el)))

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)
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);
.. it would search for car in ape,dog,pig,
.. then it would search for planet in dog,pig (remember index is now 1 so you are telling .includes() to ignore ape)
.. then it would search for apple in pig (index is now 2 so .includes() ignores ape and dog)
.. 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.
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 an arrow function (or "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.)
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.

What is the following code snippet from a slate.js example doing?

I am trying to understand Slate.js by looking through the rich text example, and I came across the following snippet of code, which I do not understand.
const isBlockActive = (editor, format) => {
const [match] = Editor.nodes(editor, {
match: n => n.type === format,
})
return !!match
}
I am not an expert in javascript, and I am new to both typescript and slate.js so I apologize in advance for not being able to frame my question better, but here is what I currently understand and what I am still unsure about:
(1) Editor.nodes() is a method which returns an Iterable. What is the "const [match]" notation? Is this javascript or typescript?
(2) Is the "match" in "const [match]" the same as the "match" in "match : n => n.type === format"? If so, does that mean "const [match]" is an array with one element which is a function? It seems odd if that were the case as then why bother making Editor.nodes() return an Iterable at all?
(3) I know double exclamation points give me a Boolean object, but since I can't wrap my head around whether match is a function or an iterable or something else, I have no idea what the truth or falsity of !!match tells me about the initial iterable returned by Editor.nodes().
Thanks for any light you may be able to shed on my confused brain!
That's called array destructuring. match is a variable that contains the first element of the array (or rather the first value yielded by the iterator) returned by the Editor.nodes function. It barely equals:
const match = Editor.nodes(...)[0];
Or more accurately:
const _it = Editor.nodes(...)[Symbol.iterator]().next();
const match = _it.done ? undefined : _it.value;
(1) Editor.nodes() is a method which returns an Iterable. What is the "const [match]" notation? Is this javascript or typescript?
It's JavaScript (and TypeScript) array destructuring. Editor.nodes returns an iterable which is iterated over to create an array, and the first element of this array is assigned to match. We're only interested in in whether there is at least one match, so looking at the first element of the array will tell us that.
(2) Is the "match" in "const [match]" the same as the "match" in "match : n => n.type === format"? If so, does that mean "const [match]" is an array with one element which is a function? It seems odd if that were the case as then why bother making Editor.nodes() return an Iterable at all?
The two variables are completely different, and could have (should have?) been named differently to make things more clear. Editor.nodes() is part of the Editor interface/API, which is used for many different things. In this case we're only interested in the first element. You could use it to find and iterate over all nodes in editor.children which your match function returns true for.
(3) I know double exclamation points give me a Boolean object, but since I can't wrap my head around whether match is a function or an iterable or something else, I have no idea what the truth or falsity of !!match tells me about the initial iterable returned by Editor.nodes().
The resulting match is a node object, or undefined if it didn't match any. An object is truthy while undefined is falsey, and doing !! just converts it to a boolean.

Why does my code not detect a match?

I have some code that is comparing a string against values in an array:
var blacklistedSites = ['https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'];
//Returns true if the current site is blacklisted, false otherwise
function isBlacklistedSite(url) {
console.log('Site is ' + url);
blacklistedSites.forEach(function(entry) {
console.log('testing ' + entry);
if (entry == document.URL) {
return true;
}
});
return false;
}
console.log(isBlacklistedSite('https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'));
This outputs:
Site is https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8
testing https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8
false
Why does isBlacklistedSite() not detect a match?
The reason your code doesn't work is that your:
return true;
effectively does nothing. It just returns from the forEach function, which happens anyway regardless of there being a match, or not. Your return true; does not return from your isBlacklistedSite() function. Your isBlacklistedSite() function always exits with:
return false;
While you could do this using .forEach(), it is a poor choice. The .forEach() method always iterates over every member of the array, regardless of any return value you provide in the forEach function. You would only use it if you were also doing some other operation on every element of the array at the same time. Even then, it might be better to separate out the two different tasks. If you did use it, you would have to keep the detection in a variable defined external to the .forEach(function(){....
Use .indexOf() to test an exact match to an array element
If you want to test for exact matches of Array elements, you can use .indexOf() and test for a value > -1.
For instance:
var blacklistedSites = ['https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'];
function isBlacklistedSite(url) {
console.log('Site is ' + url);
return blacklistedSites.indexOf(url) > -1;
}
//Test with a known matching value.
console.log(isBlacklistedSite(blacklistedSites[0]));
//Test with a known failing value.
console.log(isBlacklistedSite('foo'));
If you need a more complex test, you can use .some()
var blacklistedSitesRegExes = [/(?:https?:)?\/\/[^/]*www\.google\.com\/.*espv=2/];
function isBlacklistedSite(url) {
console.log('Site is ' + url);
return blacklistedSitesRegExes.some(function(regex){
regex.lastIndex = 0; //Prevent contamination from prior tests
return regex.test(url);
});
}
//Test with a known matching value.
console.log(isBlacklistedSite('https://www.google.com/_/chrome/newtab?espv=2&ie=UTF-8'));
//Test with a known failing value.
console.log(isBlacklistedSite('foo'));
With limited availability: .includes() (not for production code)
.includes() does exactly what you want (return a Boolean true/false for an exact match). However, it is not as generally available as .indexOf(). It is recommended not to use it in production code. For Arrays, it does not add much benefit over .indexOf(url) > -1.
Additional methods
There are many additional methods available to Arrays which could be used to determine that you have a match. What you use will depend on your specific needs. As always, you should be mindful of compatibility issues for any method you choose to use. Some of the available methods are (text from MDN):
Array.prototype.every()
Returns true if every element in this array satisfies the provided testing function.
Array.prototype.filter()
Creates a new array with all of the elements of this array for which the provided filtering function returns true.
Array.prototype.find() (no IE)
Returns the found value in the array, if an element in the array satisfies the provided testing function or undefined if not found.
Array.prototype.findIndex() (no IE)
Returns the found index in the array, if an element in the array satisfies the provided testing function or -1 if not found.
Array.prototype.includes() (compatibility issues, including no IE)
Determines whether an array contains a certain element, returning true or false as appropriate.
Array.prototype.indexOf()
Returns the first (least) index of an element within the array equal to the specified value, or -1 if none is found.
Array.prototype.lastIndexOf()
Returns the last (greatest) index of an element within the array equal to the specified value, or -1 if none is found.
Array.prototype.some()
Returns true if at least one element in this array satisfies the provided testing function.
Could be used, but not very appropriate:
Array.prototype.forEach()
Calls a function for each element in the array.
Array.prototype.map()
Creates a new array with the results of calling a provided function on every element in this array.
Array.prototype.reduce()
Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value.
Array.prototype.reduceRight()
Apply a function against an accumulator and each value of the array (from right-to-left) as to reduce it to a single value.

How does this unique() function work

As I was looking at a unique() function I found
which takes an array as argument and returns a new array which contains the unique elements of this array (which means no duplicated items). However I cannot understand the logic of this function. Can somebody explain it to me?
Here is the function:
function unique ( array ) {
return array.filter(function(a){
return !this[a] ? this[a] = true : false;
}, {});
}
I can't really understand the whole code especially the !this[a] ? this[a] = true : false; and the new object ({}) that is passed as the second argument to filter.
Let's start with the filter:
The filter() method creates a new array with all elements that pass
the test implemented by the provided function.
The a is the random number of the array to which you apply the filter. The whole essence is in the following statement:
return !this[a] ? this[a] = true : false;
If the this[a] is true, a has been already processed once and it has been added to this as one of its properties. Otherwise, this[a] is false. So taking its negation result in true and the current a should be returned. Furthermore this[a] would be set to true and then we proceed to the next a.
The following snippet will help you grasp what filter does:
var numbers = [1,2,3,4,5];
var filteredNumbers = numbers.filter(function(number){
console.log(number);
return number > 2;
});
console.log(filteredNumbers);
And the following snippet will show you in action what happens in unique function:
function unique ( array ) {
return array.filter(function(a){
console.log(this);
return !this[a] ? this[a] = true : false;
}, {});
}
var array = [1,2,3,1,2,3,4,5,5,6];
console.log(unique(array));
I understand the basic logic of filter but what i dont is the {}
passed as a 2nd argument and how each value is added to a new array
with !this[a]
The second argument is an optional value that you can pass to the filter method and it can be used as the this, when your callback would be executed (check the link I mentioned at the beginning about filter). You pass there an empty object. When you use the keyword this inside your callback, your refer this object. This is why the first time that code gets in this method returns {}. Check the first line of the output of the second snippet.
I will explain the second part of your question based on the second snippet. The first time you get in you have an empty object (I refer to this) and the first number processed is 1. So this1 would be undefined. So !this[1] would be true. Hence the first part after the ? is executed which is an assignment
this[1] = true.
Now this acquired its first key, 1, with value true. Furthermore, 1 would be returned from filter. The same happens with 2 and 3. When we arrive at 1 the
!this[1]
is false, since this[1] is true. So false is returned and the 1 now would not be added to the array that would be returned after all elements of array have been processed.
Basically, .filter would call the callBack function by supplying the individual values of the iterating array. If the callBack returns a value that resolves to true then that value will be collected, else that particular value will be ignored.
Here the second argument of filter has been used. That second argument will be used as a context(this) while calling the callBack internally. So here in your code, the passed object will be added with the array's value as property for each iteration. And in the consecutive iterations, the code will check the current value is available as a property in the initially passed object. If available then that ternary operator would return false, otherwise true.
Hence the unique values will be returned from the filter function.
Array.filter will only grab elements of the array when the function passed return truthy.
For each element of the array it is doing
return !this[a] // if value is not yet on this
? this[a] = true // add value to this and return true (grab element)
: false; // value was already cached, so return false (don't grab)
So it will only return 1 of each
Other answers have explained basically how this works. But a couple of points:
First, instead of return !this[a] ? this[a] = true : false;, it would be more concise to write
!this[a] && (this[a] = true)
Second, this code has the flaw that it only works on elements which can serve as keys into an object--basically strings or numbers. That is why using Set is better:
Array.from(new Set(array))
The above will work on any primitive or object.
Third, this approach does not work properly with strings and numbers. If the number 1 is present, it will filter out the string "1".
const uniq = a => a.filter(x => !this[x] && (this[x] = true), {});
console.log(uniq([1, '1']));
The reason is that object keys are string-valued.
Finally, IMHO this code is a bit too tricky for its own good. Most developers, even experienced ones, would stop and scratch their heads for a minute before figuring it out. Less experienced developers would have to consult the documentation for the thisArg parameter to Array#filter before being able to understand it. The ternary operator with the assignment inside is also a bit cryptic. I'd go ahead and write it out as
if (this[x]) return false;
this[x] = true;
return true;

Categories

Resources