This question already has answers here:
Function with forEach returns undefined even with return statement
(5 answers)
Closed 3 years ago.
So I have a little bit of confusion, I'm solving a challenge on freeCodeCamp.
The challenge reads as follows
Everything Be True
Check if the predicate (second argument) is truthy on all elements of a collection (first argument).
It's solved but I don't understand why I had to take an extra step. My code is as such:
function truthCheck(collection, pre) {
collection.forEach(function(element) {
for (key in element) {
if (!element.hasOwnProperty(pre)) {
return false;
} else if (key === pre) {
if (!Boolean(element[key])) {
return false;
}
}
}
});
return true;
}
truthCheck([
{"user": "Tinky-Winky", "sex": "male"},
{"user": "Dipsy"},
{"user": "Laa-Laa", "sex": "female"},
{"user": "Po", "sex": "female"}
], "sex");
so in this instance it should fail because the 2nd element within collection does not have the sex property. Also you will receive a fail if the pre argument, or in this case sex is not a truthy value.
When these get hit (Which they are, I'm able to tell through console logs) but I figure it would break out of the loop and return from the truthCheck function.....but it doesn't, and it will eventually return true.
I was able to circumvent this by defining a variable and then setting that value to false and then returning the variable at the end. Is there a better way? It seems like these returns should break out of the truthCheck function? Am I missing something?
As the other answers explain, this is meaningless:
collection.forEach(function () {
// do something
return false;
});
because array#forEach simply does not care for the return value of its worker function. It just executes the worker function for each array element.
You could use the worker function to set an outer variable:
function truthCheck(collection, pre) {
var allAreTruthy = true;
collection.forEach(function (elem) {
// if this ever flips allAreTruthy to false, it will stay false
allAreTruthy = allAreTruthy && elem[pre];
});
return allAreTruthy;
}
But there are better ways to express this.
Check if the predicate (second argument) is truthy on all elements of a collection (first argument).
Could be paraphrased as "Every element of the collection has a truthy value at a particular key."
function truthCheck(collection, pre) {
return collection.every(function (elem) { return elem[pre]; });
}
Could be paraphrased as "None of the elements of the collection have a falsy value at a particular key (or are missing the key entirely)."
Or, since an Array#none method does not actually exist, "There aren't some elements of the collection that have a falsy value at a particular key."
function truthCheck(collection, pre) {
return !collection.some(function (elem) { return !elem[pre]; });
}
The advantage of using Array#some is that it stops iterating the array as soon as the condition it seeks for is fulfilled. If your array had many elements this would mean improved performance. For short arrays there's not a lot of difference to using Array#every or Array#forEach.
The above is semantically equivalent to
function truthCheck(collection, pre) {
var i;
for (i = 0; i < collection.length; i++) {
if (!collection[i][pre]) return false;
}
return true;
}
Since JS objects simply return undefined when you access a key that has not been set, a check for hasOwnProperty is superfluous here.
You can't return anything from a ForEach loop. It will return undefined by default.
As the official documentation, Array.prototype.forEach() - JavaScript | MDN, says:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool, use a plain loop instead. If you are testing the array elements for a predicate and need a Boolean return value, you can use every() or some() instead.
So you can use a very simple for..in loop, for example:
for(var c in collection){
// Do whatever you want
}
[collection].forEach of javascript does not work like an ordinary loop. There is no way to prematurely end it not unless you make it throw an exception.
The behavior that you are expecting is what you would expect out of a javascript for loop but since forEach uses callback functions for each looped object, then you are only exiting the callback function instead of the forEach. Also it is worth noting that in your code, the you have a for loop which has a return in it. The return block in this loop only breaks this loop and not the forEach (which I have mentioned earlier, cannot be prematurely terminated unless otherwise)
As you can see forEach is mostly meant to iterate all elements instead of conditional checks per iterated element.
function truthCheck(collection, pre) {
return collection.every(function (person) { return !!person[pre]; });
}
you execute a function on each element of the collection. This function checks that element an returns something. But that returned value does not affect the result of the outer function. Since the outer function does not depend on the inner function your result is allways true.
if you define a variable, set this to false and return that variable at the end would works but it would be inefficient. Lets think of the following scenario. You found one element which does not has the target key. So right now you should return but you can't. You have to working yourself through the whole collection. The forEach loop does not give you the chance to exit without a mess. Thus a better idea is the for loop. Your can exit exit the for loop if found what you were looking for
a slightly easier way would be:
function truthCheck(collection, pre) {
//iterate thrugh your collection
for (var c in collection){
//get keys of element as an array and check if pre is in that array
if( Object.keys(collection[c]).indexOf(pre) == -1){
// pre was not found
return false;
}
}
return true;
}
The book says:
To run over a list (in listToArray and nth), a for loop specification like this can be used:
for (var node = list; node; node = node.rest) {}
Every iteration of the loop, node points to the current sublist, and the body can read its value property to get the current element. At the end of an iteration, node moves to the next sublist. When that is null, we have reached the end of the list and the loop is finished.
Question 1: Could you explain how the condition of the for loop works? I understand that it is checking whether the node(the current list) is null.. but how does the "node" argument by itself work?
Question 2: why doesnt the following code work?
function listToArray(list){
var result = [];
while(list.value != null){
result.push(list.value);
list = list.rest;
}
return result;
};
console.log(listToArray(list));
To understand how this works, you need to know two things:
How java script For loop works.
What are truthy values.
The syntax for the for loop statement:
for ([initialization]; [condition]; [final-expression]) statement
[condition] An expression to be evaluated before each loop iteration.
If this expression evaluates to true, statement is executed. If the
expression evaluates to false, execution skips to the first expression
following the for construct.
In Java script, a truthy value evaluates to true when evaluated in a Boolean context.
All values are truthy unless they are defined as falsy (i.e., except
for false, 0, "", null, undefined, and NaN).
So till node is null at one point, we may say that node is truthy and evaluates to true.
When the statement, node = node.rest assigns a null value to node, there the for loop exits.
The below code does not work because, list.value may be undefined, or as a fact any other falsy value other than null.
function listToArray(list){
var result = [];
while(list.value != null){
result.push(list.value);
list = list.rest;
}
return result;
};
console.log(listToArray(list));
instead try, while(list.value).
...but how does the "node" argument by itself work?
node is a variable. A variable is resolved to its value. E.g. if I have
var foo = 3;
alert(foo + 1);
it will resolve foo to the value 3 and then add 1 to it. Similarly in this case, node is resolved to whatever value it has, that value is converted to a Boolean value (i.e. Boolean(node)) and then tested whether it is true or false.
If the value of node is null, then Boolean(node) will return false.
why doesnt the following code work?
Can't really tell without knowing what list is and what exactly "does not work".
But presumably, list.rest is null at some point, in which case you would try to access null.value which throws an error. The equivalent version to the for loop would be to compare list itself:
while (list != null)
A simpler way is to use:
var result = Array.prototype.slice.call(list);
this will turn the list into an array
function listToArray(listValue){
var arrayResult = [];
while(listValue.list){
arrayResult.push(listValue.value);
listValue = listValue.list;
}
arrayResult.push(listValue.value);
return arrayResult;
}
I want to search a string in a nested JSON object. If the string found in an object, I need to return that object.
I am using a recursive function to achieve this. The problem is, the function is recursing until the end and not returning the object found.
Please see the entire code in jsfiddle
function search(obj, name) {
console.log(obj["name"], ",", name, obj["name"] == name);
if (obj["name"] == name) {
return obj; //NOT RETURNING HERE
}
if (obj.children || obj._children) {
var ch = obj.children || obj._children;
//console.log(ch);
ch.forEach(function(val) {
search(val, name)
});
}
return -1;
}
search(myJson, "VM10-Proc4")
I am not sure what is going wrong.
You need to stop looping over children when it finds one that matches.
function search(obj, name) {
console.log(obj.name, ",", name, obj.name == name);
if (obj.name == name) {
return obj;
}
if (obj.children || obj._children) {
var ch = obj.children || obj._children;
for (var i = 0; i < ch.length; i++) {
var found = search(ch[i], name);
if (found) {
return found;
}
}
}
return false;
}
FIDDLE demo
The correct return value is getting lost in the chain of recursive function calls. After the correct value is found, any additional searches made will return incorrect values from that point on.
A couple ways to handle this:
1. Cancel the search
When the correct value is found, immediately return it all the way up the recursive stack, without searching any more of the current arrays or the nested arrays. In other words, cancel the rest of the search.
#Barmer's answer is an example of this. The key part of his code is the use of for loops rather than the each method to iterate through the arrays, since it's much easier to interrupt a for loop.
2. Store the value somewhere safe
When the correct value is found, store it somewhere safe, allow the rest of the search to continue, and after the initial function call has finished access the value. The simplest way is to store the correct value in a global variable, though that's not a good practice as it violates the encapsulation of the function.
#shyam's answer presents a cleaner solution: Passing a reference to a global variable as a function parameter, setting the parameter when the correct value is found, and then accessing the global variable after the initial function call has finished.
Choosing between the two
In laymen's terms, the intended logic of the function could be summed up as follows: When you find what you're looking for, stop, and let me know what it is immediately. The only reason to continue searching would be if multiple pieces of data needed to be found. I'm assuming that's not the case here.
Of the two approaches, #2 is a quick-fix workaround that should work fine but will further confuse anyone who's trying to understand the intended logic of the function. Why is the search continuing if it's only looking for a single piece of data that's already been found?
#1 is a refactoring of the function so that it behaves more consistently with the intended logic, which would make the function easier to understand. The function stops searching when it finds what it needs.
I know this is an old post, but I can see 2 issues with this:
1) the recursive call isn't returning the result up the call stack i.e.
search(val, name)
Should be
return search(val, name)
2) it looks like you are using array.forEach(). The documentation states:
There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool. Use a plain loop instead. If you are testing the array elements for a predicate and need a Boolean return value, you can use every() or some() instead. If available, the new methods find() or findIndex() can be used for early termination upon true predicates as well.
What this means is, effectively, when you find the result you are looking for you want to send it back up the call stack. Using array.forEach will continue to recursively look through the hierarchy and return all values, not just the one you are interested in. Therefore the last returned value maybe one you don't expect, like undefined! Therefore use a different method of iterating i.e.
for (var i = 0; i < ch.length; i++) {
var val = ch[i];
return search(val, name, ret);
}
The accepted answer does spoon feed you part of this answer but doesn't explain why. Hence this answer
Since you are recursing the return might be nested too deep for you to get a meaningful result. Instead you can try passing an extra argument to collect the results.
function search(obj, name, ret) {
console.log(obj["name"], ",", name, obj["name"] == name);
if (obj["name"] == name) {
ret.push(obj);
return
}
if (obj.children || obj._children) {
var ch = obj.children || obj._children;
ch.forEach(function(val) {
search(val, name, ret);
});
}
}
var result = [];
search(myJson, "VM10-Proc4", result)
Here is a solution using object-scan
// const objectScan = require('object-scan');
const myJson = {"name":"UCS - San Jose","type":"Minor","children":[{"name":"VM1","type":"Clear","children":[{"name":"VM1-Proc1","type":"Clear","children":[{"name":"VM1-Proc1-child1","type":"Clear"}]},{"name":"VM1-Proc2","type":"Clear"},{"name":"VM1-Proc3","type":"Clear"},{"name":"VM1-Proc4","type":"Clear"},{"name":"VM1-Proc5","type":"Clear"},{"name":"VM1-Proc6","type":"Clear"},{"name":"VM1-Proc7","type":"Clear"},{"name":"VM1-Proc8","type":"Clear"},{"name":"VM1-Proc9","type":"Clear"},{"name":"VM1-Proc10","type":"Clear"}]},{"name":"VM2","type":"Clear","children":[{"name":"VM2-Proc1","type":"Clear"},{"name":"VM2-Proc2","type":"Clear"},{"name":"VM2-Proc3","type":"Clear"},{"name":"VM2-Proc4","type":"Clear"},{"name":"VM2-Proc5","type":"Clear"},{"name":"VM2-Proc6","type":"Clear"},{"name":"VM2-Proc7","type":"Clear"},{"name":"VM2-Proc8","type":"Clear"},{"name":"VM2-Proc9","type":"Clear"},{"name":"VM2-Proc10","type":"Clear"}]},{"name":"VM3","type":"Clear","children":[{"name":"VM3-Proc1","type":"Clear"},{"name":"VM3-Proc2","type":"Clear"},{"name":"VM3-Proc3","type":"Clear"},{"name":"VM3-Proc4","type":"Clear"},{"name":"VM3-Proc5","type":"Clear"},{"name":"VM3-Proc6","type":"Clear"},{"name":"VM3-Proc7","type":"Clear"},{"name":"VM3-Proc8","type":"Clear"},{"name":"VM3-Proc9","type":"Clear"},{"name":"VM3-Proc10","type":"Clear"}]},{"name":"VM4","type":"Minor","children":[{"name":"VM4-Proc1","type":"Clear"},{"name":"VM4-Proc2","type":"Clear"},{"name":"VM4-Proc3","type":"Minor"},{"name":"VM4-Proc4","type":"Clear"},{"name":"VM4-Proc5","type":"Clear"},{"name":"VM4-Proc6","type":"Minor"},{"name":"VM4-Proc7","type":"Clear"},{"name":"VM4-Proc8","type":"Clear"},{"name":"VM4-Proc9","type":"Clear"},{"name":"VM4-Proc10","type":"Clear"}]},{"name":"VM5","type":"Clear","children":[{"name":"VM5-Proc1","type":"Clear"},{"name":"VM5-Proc2","type":"Clear"},{"name":"VM5-Proc3","type":"Clear"},{"name":"VM5-Proc4","type":"Clear"},{"name":"VM5-Proc5","type":"Clear"},{"name":"VM5-Proc6","type":"Clear"},{"name":"VM5-Proc7","type":"Clear"},{"name":"VM5-Proc8","type":"Clear"},{"name":"VM5-Proc9","type":"Clear"},{"name":"VM5-Proc10","type":"Clear"}]},{"name":"VM6","type":"Minor","children":[{"name":"VM6-Proc1","type":"Clear"},{"name":"VM6-Proc2","type":"Clear"},{"name":"VM6-Proc3","type":"Minor"},{"name":"VM6-Proc4","type":"Clear"},{"name":"VM6-Proc5","type":"Clear"},{"name":"VM6-Proc6","type":"Clear"},{"name":"VM6-Proc7","type":"Minor"},{"name":"VM6-Proc8","type":"Clear"},{"name":"VM6-Proc9","type":"Clear"},{"name":"VM6-Proc10","type":"Clear"}]},{"name":"VM7","type":"Clear","children":[{"name":"VM7-Proc1","type":"Clear"},{"name":"VM7-Proc2","type":"Clear"},{"name":"VM7-Proc3","type":"Clear"},{"name":"VM7-Proc4","type":"Clear"},{"name":"VM7-Proc5","type":"Clear"},{"name":"VM7-Proc6","type":"Clear"},{"name":"VM7-Proc7","type":"Clear"},{"name":"VM7-Proc8","type":"Clear"},{"name":"VM7-Proc9","type":"Clear"},{"name":"VM7-Proc10","type":"Clear"}]},{"name":"VM8","type":"Clear","children":[{"name":"VM8-Proc1","type":"Clear"},{"name":"VM8-Proc2","type":"Clear"},{"name":"VM8-Proc3","type":"Clear"},{"name":"VM8-Proc4","type":"Clear"},{"name":"VM8-Proc5","type":"Clear"},{"name":"VM8-Proc6","type":"Clear"},{"name":"VM8-Proc7","type":"Clear"},{"name":"VM8-Proc8","type":"Clear"},{"name":"VM8-Proc9","type":"Clear"},{"name":"VM8-Proc10","type":"Clear"}]},{"name":"VM9","type":"Clear","children":[{"name":"VM9-Proc1","type":"Clear"},{"name":"VM9-Proc2","type":"Clear"},{"name":"VM9-Proc3","type":"Clear"},{"name":"VM9-Proc4","type":"Clear"},{"name":"VM9-Proc5","type":"Clear"},{"name":"VM9-Proc6","type":"Clear"},{"name":"VM9-Proc7","type":"Clear"},{"name":"VM9-Proc8","type":"Clear"},{"name":"VM9-Proc9","type":"Clear"},{"name":"VM9-Proc10","type":"Clear"}]},{"name":"VM10","type":"Clear","children":[{"name":"VM10-Proc1","type":"Clear"},{"name":"VM10-Proc2","type":"Clear"},{"name":"VM10-Proc3","type":"Clear"},{"name":"VM10-Proc4","type":"Clear"},{"name":"VM10-Proc5","type":"Clear"},{"name":"VM10-Proc6","type":"Clear"},{"name":"VM10-Proc7","type":"Clear"},{"name":"VM10-Proc8","type":"Clear"},{"name":"VM10-Proc9","type":"Clear"},{"name":"VM10-Proc10","type":"Clear"}]}]};
const search = (obj, name) => objectScan(['**.name'], {
rtn: 'parent',
abort: true,
filterFn: ({ value }) => value === name
})(obj);
console.log(search(myJson, 'VM10-Proc4'));
// => { name: 'VM10-Proc4', type: 'Clear' }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan
the difference (speed, performace, side effects, ...) between implementations of the for loop:
between
var i;
for(i = 0; i < length; i++){ //Do something}
// more code
and
for(var i = 0; i < length; i++){ //Do something}
// more code
and
for(i = 0; i < length; i++){ //Do something}
// more code
and between
var e;
for( e in array){ //Do something}
// more code
and
for(var e in array){ //Do something}
// more code
and
for(e in array){ //Do something}
// more code
There is no difference.
JavaScript variables have only function scope, and although you can place a var statement in the initialisation part of a for loop in reality the declaration is "hoisted" to the top of the scope such that when your second case examples run they're treated exactly like the first cases.
EDIT: Since you updated the question after I answered, the third syntax that you added where you don't use the var keyword means the variable i (or e) will be created as a global - unless it already exists as a global in which case the existing variable will be overwritten. Global variable access is slower than local variable access.
NOTE: my interpretation of the question is that it is not comparing a standard for loop with the for..in variant, it is just compared the different variable declaration methods with each other for a standard for loop, then doing the same again for a for..in loop.
There is no difference in relation to the declariation of your counter variable..
BUT ALWAYS DECLARE YOUR VARIABLES WITH var
Otherwise they pollute javascript's already dirty global scope...
As far as for...in vs traditional for look here...
(Which is my answer to the duplicate question...)
Yes, there is a difference between for loop and for/in loop in javascript. Here is what is different
consider this array
var myArr = ["a", "b", "c"];
Later, I add an element to this array, but in a different way, like so:
myArr[myArr.length + 1] = "d";
At this point of time, this is what the array looks like, if you console.log it
["c", "b", "a", undefined × 1, "d"]
Now, let us loop through the array using for and for/in loop and see what the difference is:
first, lets try the for loop
for(var i = 0; i != myArr.length; i++) { // note, i used " != " instead of " < ". Yes this is faster.
console.log(myArr[i]);
}
// output will look like this:
// "a"
// "b"
// "c"
// undefined × 1
// "d"
// - This still shows the undefined value in the array.
Now, lets look at the for/in loop
for(key in myArr) {
console.log(myArr[key]);
}
// array will look like:
// "a"
// "b"
// "c"
// "d"
// This will skip the undefined value.
Difference 1: The javascript interpreter will skip all values that are null or undefined when using the for/in loop. Also, the for/in loop will convert all values if it encounters a primitive value, to its equivalent wrapper object. Where as the for loop doesn't do this.
Difference 2: When using the for loop, we declared the variable i within the loop, this variable will be scoped within a function, if the for loop is written within that function. Which means, until the function ends, the variable i is still available, even outside of the for loop, but within that function. Where as, in case of the for/in loop, the scope of the "key" variable dies immediately after the for/in loop stops executing, which means, there is less memory utilization.