Here's an example:
function try2RecurseIt() {
var MyArr = [[1,[2,3],4],[5,6],[7,8,[9,0]]];
letsDance(MyArr);
}
function letsDance(arr) {
for (i=0; i<arr.length; i++) {
console.log("arr["+i+"] "+ arr[i]);
if (arr[i].constructor.toString().indexOf("Array") > -1) { // isArray check
letsDance(arr[i]);
}
}
}
I expected it would loop through all elements, but the result:
// arr[0] 1,2,3,4
// arr[0] 1
// arr[1] 2,3
// arr[0] 2
// arr[1] 3
Somewhere my letsDance() function loses context and I can't figure out where. What am I doing wrong?
Yes, I know about forEach, but I'm trying to understand where I'm wrong here. Further I'm going to use this method to parse htmlElements.children[n] which are not arrays.
I'm learning JavaScript and need to understand the basics without using any libraries.
Your problem is failure to keep variables local:
for (i=0; i<arr.length; i++) {
here, i becomes global on the first loop so subsequent loops increment it, use:
for (var i=0; i<arr.length; i++) {
The bug in your code is that i is a global variable. Therefore each call to the function rewrites the same i and i keeps being reset to 0.
Related
I'm going through Chapter 4 of Eloquent JavaScript in case anyone wants to see exactly what's going on. https://eloquentjavascript.net/04_data.html.
I'm trying to understand where this For loop grabs its length value from.
for (let event of journalEvents(JOURNAL)) {
console.log(event + ":", phi(tableFor(event, JOURNAL)));
As I understand from earlier in the chapter, this For loop could also be written as:
for (let i = 0; i < journalEvents(JOURNAL).length; i++) {
let event = journalEvents(JOURNAL)[i];
console.log(event + ":", phi(tableFor(event, JOURNAL)));
}
This means the length can be found somewhere in the function journalEvents() when passed the arguement JOURNAL.
function journalEvents(journal) {
let events = [];
for (let entry of journal) {
for (let event of entry.events) {
if (!events.includes(event)) {
events.push(event);
}
}
}
return events;
}
At this point, I'm a little lost. I am guessing I need to determine the value of journalEvents(JOURNAL). Is it going to be the return value of the function?
The return value is the array events[]. Does this mean the length value is the length of events[]?
The answer is yes, the length value is that of the events array. However, length is not really a direct factor here. Arrays are iterables, so you can iterate through them with for...of loops. You can easily test this by changing the function journalEvents so that it returns a number, and see what happens :)
The function journalEvents(journal) returns an array, which is added to inside the function. Basically this is the same as putting the array that the function returns wherever you put journalEvents(journal):
for (let event of [something]) {
console.log(event + ":", phi(tableFor(event, JOURNAL)));
}
Notice that the for…of loop only would call journalEvents(JOURNAL) once, and then iterates of the result value. If you want to rewrite it to a "normal" loop, you should introduce a temporary variable for that:
const events = journalEvents(JOURNAL);
for (let i = 0; i < events.length; i++) {
const event = events[i];
console.log(event + ":", phi(tableFor(event, JOURNAL)));
}
Does this mean the length value is the length of events[]?
Yes.
i have write a forEach implemention for understand this Response:
function myForeach(sourch, func) {
for (var i = 0, len = sourch.length; i < len; i++) {
func(sourch[i], i, arr);
}
}
and like forEach, its slow than simple for-loop:
for (var i = 0, len = arr.length; i < len; i++) {
(function(item) {
action(item);
})(arr[i], i, arr); //exactly what foreach dose!
}
here, the two way have function setup & teardown.
why the for its so faster?
I think it's largely related to the fact that your action() is a no-op which gets optimized away by the runtime in some cases, but not in other cases.
If you change your action to actually do something (e.g. add the array values to a sum variable), the differences are a lot smaller.
Here are my updated benchmark results, also on Chrome:
When declaring an anonymous function in a loop i.e. 'function() { // doStuff}' the interpreter has to define a new function at runtime every iteration of the loop, thus creating alot of overhead for the interpreter.
Does make sense to check if the length of the array is not equal to 0 before of to do the loop?
var arr = [1,2,3];
if (arr.length !== 0) {
// do loop
}
In the general case it doesn't make sense. For a foreach, for, or while loop, the loop condition will prevent the loop executing anyway, so your check is redundant.
var arr = [];
for (var loopCounter = 0; loopCounter < arr.length; loopCounter++)
{
// This loop will never be entered.
}
foreach (var element in arr)
{
// This loop will never be entered.
}
var loopCounter = 0;
while (loopCounter < arr.length)
{
// This loop will never be entered.
loopCounter++;
}
However, the only time it is important is if you are using a do...while loop. In this scenario, the loop executes, and then you check your loop-condition (when it is too late). It's likely that your code would have thrown an exception inside the loop in this case, as demonstrated in the following code.
var arr = [];
var loopCounter = 0;
do
{
someMethod(arr[loopCounter]); // This will throw an error
loopCounter++;
}
while(loopCounter < arr.length);
No, the check is unnecessary. If the array has zero elements, the loop will execute zero times (assuming it is written correctly).
If the array could be null, then checking for that would be valid, e.g.
if(arr !== null)
{
// loop
}
Well, it depends on the loop. If it's of the form:
for (i = 0; i < arr.length; i++) {
do_something_with (arr[i]);
}
then, no, the if is superfluous, the for loop body will not execute in this case.
However, if you've done something like (and this seems likely given that you mention a do loop, although you may have actually meant "do the loop", so it's hard to say):
i = 0;
do {
do_something_with (arr[i]);
i++;
while (i < arr.length);
then, yes, you'll need it to avoid the problem of using arr[0] if no such beast exists.
But I would consider that second case (check-after) "broken" since the while () {} or for () {} variants (check-before) would be more natural in that case.
I have the following small code snippet with the following expected and real output. My question is quiet simple. Why is it printing it this sequence? and how to I print the expected output?
Gr,
expected result:
0
1
2
0
1
2
real result:
0
1
2
3
3
3
this is the code:
var functions = [];
for (var i=0; i<10; i++) {
console.log (i);
functions.push (function () {
console.log (i);
});
};
for (var j=0; j<functions.length; j++) {
functions[j] ();
};
The functions that you push into the array doesn't log the value of i as it was when the function was created, they log the value of i at the time that the function is called.
Once the first loop ends, the value of i is 10, therefore any of the functions called after that will log the value 10.
If you want to preserve the value of i at different states, you can use a closure to make a copy of the value:
for (var i=0; i<10; i++) {
console.log (i);
(function(){
var copy = i;
functions.push (function () {
console.log (copy);
});
})();
};
The local variable copy will get the value of i and retain the value. You can also pass the value as a parameter to the function:
for (var i=0; i<10; i++) {
console.log (i);
(function(copy){
functions.push (function () {
console.log (copy);
});
})(i);
};
The expected result should be:
1
2
...
10
10
10
... 7 more times
The reason for this is simple. The console.log(i) inside your loop is correctly printing the value of i at each iteration of the loop. When you create and push a function into the functions array, what you're doing is closing each of those functions over the same variable i. At the end of your loop, i no longer satisfies your loop condition, so i = 10 is true. As a result, since each of those functions is going to execute console.log(i), and they're each closed over the same i, which now has value 10, you should expect to see the value 10 printed 10 times.
To prevent this, you will want to make a function which returns a function rather than creating functions directly in a loop:
var functions = [], i, j;
function createEmitter(i) {
return function () {
console.log(i);
};
}
for (i = 0; i < 10; i++) {
console.log(i);
functions.push(createEmitter(i));
};
for (j = 0; j < functions.length; j++) {
functions[j]();
};
Now, each of those created functions is closed over its own private scope variable, which resolves the problem.
You should update your code example to be i < 3 so that your results and function match up.
The functions you push into the functions array are storing a reference to the variable i, which after executing the top loop, is 10. So when it executes, it will go get the variable i (which is 10) and print that 10 times.
Here's a good way to see this in action:
for (var i=0; i<10; i++) {
console.log (i);
};
console.log(i) //=> 10
When you are using a variable, remember that the variable can change, it's not frozen at it's current value. You are only holding on to a reference to something else.
To fix this problem, I would run this type of minor refactor on the code (because the other answers have already created an extra scope, figured I'd give you something different). Rather than storing 10 functions, just store the numbers and execute them with a single function. This is a more elegant way to write it anyway, and takes up less space. I'm sure this example was abstracted from whatever code was really giving you the problem, but the general pattern still applies.
numbers = [];
for (var i=0; i<10; i++) {
console.log (i);
numbers.push(i);
};
numbers.forEach(function(i){
console.log(i);
});
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I am reading an article (JavaScript Closures for Dummies) and one of the examples is as follows.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList();
When testList is called, an alert box that says "item3 undefined". The article has this explanation:
When the anonymous functions are called on the line fnlist[j](); they all use the same single closure, and they use the current value for i and item within that one closure (where i has a value of 3 because the loop had completed, and item has a value of 'item3').
Why does item have a value of 'item3'? Doesn't the for loop end when i becomes 3? If it ends shouldn't item still be 'item2'? Or is the variable item created again when testList calls the functions?
You're close...
Why does item have a value of 'item3'? Doesn't the for loop end when i becomes 3?
Yes.
If it ends shouldn't item still be
'item2'?
Nope. This example is a little tricky. During the last iteration of the loop, i is 2, but it references the 3rd element of the list array, which is 3. In other words, item == 'item' + list[2] == 'item3'
Or is the variable item created again when testList calls the functions?
No, you were almost right the first time. I think you just missed that item[2] has the value of 3.
The for loop within buildList completes before you do the following:
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
... therefore, by that time (when you call each function), the variable item will be whatever was last assigned to it (i.e. "item3"), and i will be 3 (as a result of the last i++ operation), and list[3] is undefined.
It's all to do with the fact that the loop completes before you call the closure'd function. To prevent this, you could create a new closure, like so:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push(
(function(item, i){
// Now we have our own "local" copies of `item` and `i`
return function() {
console.log(item + ' ' + list[i])
};
})(item, i)
);
}
return result;
}
I think the point you are missing is that list[i] is underfined because i is 3, and list is only defined for 0..2.
The list variable is stored in closure as you say.
Actually you can access the list variable, but you are trying to access list[3]. After all, the i variable is also stored as a closure and it's value is 3 when the console.log function is called.
The loop ends when i becomes 3, but the "item" variable stored in the closure, and displayed by alert, is set to
var item = 'item' + list[i];
the text 'item' + the value at list[2]. The third list item is 3, so the text is item3