Can someone please explain why does it console log out 10, 10, 10 instead of 9, 9, 9?
When it goes through for loop shouldn't it stop at 9?
var foo = [];
for (var i = 0; i < 10; i++) {
foo[i] = function() {
return i;
};
};
console.log(foo[0]());
console.log(foo[1]());
console.log(foo[2]());
Whenever any function which is using any variable from parent scope is executed, it gets that value of a variable which it is holding at the time of function execution. In your case i is already reached to 10 at the time of execution because of i++.
For getting expected result, you can add IIFE to it, which will hold the value of i in its scope.
var foo = [];
for (var i = 0; i < 10; i++) {
foo[i] = (function(i) {
return function() {
return i;
};
})(i);
};
console.log(foo[0]());
console.log(foo[1]());
console.log(foo[2]());
You can use this syntax which keeps the context
for (let i = 0; i < 10; i++) {
foo[i] = () => i;
};
console.log(foo[0]());
console.log(foo[1]());
console.log(foo[2]());
https://jsfiddle.net/bandpay/8zat3bnn/
Related
I'm sure I'm doing something dumb here, but I'm not sure what. I'm adding anonymous functions to an array for later execution, and creating them in a for loop with variables that change each iteration.
I'm expecting the logged values to be:
https:server.net/a
https:server.net/b
but instead I'm getting:
https:server.net/b
https:server.net/b
It looks like maybe when I redefine the function it overwrites the last version of it, but I'm not sure why. I would think that each anonymous function would be different.
Why? What am I doing wrong here?
Here's some sample code:
f = [];
items = ['a', 'b'];
for(var i = 0; i < items.length; i++){
var itemID = items[i];
var itemAccessUrl = `https://server.net/${itemID}`;
var func = function(){
console.log(itemAccessUrl);
};
f.push(func);
}
console.log(f.length);
for(var j = 0; j < f.length; j++){
func();
}
This is because of the nature of var scoping. var is not block scoped, it is "hoisted", as if you declared it at the top of your function.
You could declare itemAccessUrl using let instead of var, which is block scoped, but it depends on what browser support you require. (Having said that, you're using templated strings, so you should be fine)
What's happening here? When you invoke your array's functions, they use the current value of itemAccessUrl, i.e. the last assigned string, with char 'b'. This because their invocation happens after the completion of first for-loop.
You can use a closure:
f = [];
items = ['a', 'b'];
for(var i = 0; i < items.length; i++){
var itemID = items[i];
var itemAccessUrl = `https://server.net/${itemID}`;
var func = (function(param) {
return function () {
console.log(param);
};
})(itemAccessUrl);
f.push(func);
}
console.log(f.length);
for(var j = 0; j < f.length; j++){
f[j]();
}
or bind your function to current param:
f = [];
items = ['a', 'b'];
for(var i = 0; i < items.length; i++){
var itemID = items[i];
var itemAccessUrl = `https://server.net/${itemID}`;
var func = (function (param) {
console.log(param);
}).bind(null, itemAccessUrl);
f.push(func);
}
console.log(f.length);
for(var j = 0; j < f.length; j++){
f[j]();
}
Furthermore you have to change the second loop, invoking f[j]();
This very good Stack Overflow answer gives an example of how closures can work counter intuitively when returning anonymous functions within a list.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + 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]();
}
}
This code will return:
item2 undefined
item2 undefined
item2 undefined
My question is - how would you modify this code so that it returns the result we're expecting?
item0 1
item1 2
item2 3
Use an IIFE in the for loop in buildList and pass in the i.
This will ensure that the i passed in will remain enclosed in the IIFE closure and will not be changed by the i in the for loop
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
//the IIFE
(function(i) {
var item = 'item' + i;
result.push(function() {
console.log(item + ' ' + list[i])
});
})(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();
This question already has answers here:
JavaScript for loop index strangeness [duplicate]
(3 answers)
Closed 9 years ago.
Take a look at this code:
var arr = new Array();
for (var i = 0; i < 10; i++) {
arr[i] = {
func:function() {
console.log(i);
}
}
}
arr[0].func();
I am confused by this, because I thought this would work. I basically would like each object in the array to print the value it was given when the function was created.
Right now when I call func(), it prints 10 in the console. Is there a way to make it print 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 instead? Thanks!
The problem is that the function inside is referencing the variable 'i' which increases every time. You can store the value of 'i' in each iteration in the object alongside the function like this:
http://jsfiddle.net/Sgnvp/
var arr = new Array();
for (var i = 0; i < 10; i++) {
arr[i] = {
func:function() {
console.log(this.value);
},
value: i
}
}
arr[0].func();
You have to create a closure to encapsulate the value. Here I'm calling consoleIt and passing it i. consoleIt creates a closure around the i
var arr = [];
function consoleIt (i) {
return function () {
console.log(i);
}
};
for (var i = 0; i < 10; i++) {
arr[i] = {
func: consoleIt(i)
};
}
arr[0].func();
This is no better really than the way that thebreiflabb did it, but it's an interesting technique that's worth knowing:
var arr = [];
for (var i = 0; i < 10; i++){
(function(i){
arr[i] = {
func : function(){
console.log(i);
}
};
})(i);
}
arr[0].func();
It just uses an anonymous closure to scope i.
I have a function that sets bunch of setTimeout functions. All functions are added to Array funcs; nevertheless, when I try to stop them using stopplay().clearTimeout the array has no values.
How to access those functions and clearTimeout on them?
var funcs = new Array();
function playWithDelay(){
for (var i = 0; i < PlayDatesArray.length; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < PlayDatesArray.length; j++) {
funcs[j]();
}
}
function createfunc(i) {
return function() {
setTimeout(function(){
//my function
}, i*1500);
};
}
function stopplay(){
alert(this.funcs.count);
for (var i = 0; i< funcs.count; i++){
//things I tried
var tmpFunction = funcs[i];
//funcs[i].splice(i, 1);
clearTimeout(tmpFunction);
clearTimeout(funcs[i]);
funcs[i]=tmpFunction;
}
}
clearTimeout takes the id returned by setTimeout, not a reference to the function itself.
So what you want is (in ES5 code)
var timeouts = [];
function createfunc(i) {
return function() {
return setTimeout(function(){
//my function
}, i*1500);
};
}
// code to create the functions
function playWithDelay(){
for (var i = 0; i < PlayDatesArray.length; i++) {
timeouts.push(createfunc(i)());
}
}
// code to stop them
function stopplay(){
timeouts.forEach(clearTimeout);
}
You're accessing this.funcs from stopplay(), but funcs is defined (at least, in this example) as a global var. Depending on what the calling code is that calls stopplay(), this is not the same scope as the global scope that funcs is created in.
Update stopplay, changing this.funcs.count to funcs to see if that is alerted with the array you created above.
Also, are you sure your funcs Array has count? I would try using length instead.
function stopplay(){
alert(funcs.length);
for (var i = 0; i< funcs.length; i++){
....
}
}
EDIT:
You're not saving the return value of setTimeout, so you can't clear the timeout. Passing clearTimeout with a function doesn't clear the timeout.
You could do something like this:
var timers = new Array();
function createfunc(i) {
return function() {
timers.push( setTimeout(function(){
//my function
}, i*1500)) ;
};
}
function stopplay(){
for (var i = 0; i< timers.length; i++){
clearTimeout(timers[i]);
}
}
I'm sure there's a really simple solution to this but I can't wrap my head around it. I'm trying to create and array of objects within a for loop like so:
for(var i = 0; i < 100; i++) {
foos[i] = new Foo(i*10);
bars[i] = someObject.createBar({
x : 0,
y : 0,
foobar = function() {
foo[i].a = true;
}
});
}
When trying to run this I get 'cannot set property a of undefined', both foos and bars are declared earlier in the code as globals.
It works fine if I create foos as foos[0] and access through bars[0]. I suspect it's something to do with function level scoping but as far as i can see the arrays should be accessible on the global object....
You need to "anchor" the value of i. To do this...
for(var i=0; i<100; i++) {
(function(i) {
// now do stuff with i
})(i);
}
Try this:
for(var i = 0; i < 100; i++) {
foos.push( new Foo(i*10) );
bars.push( someObject.createBar({
x : 0,
y : 0,
foobar = function() {
foo[i].a = true;
}
}) );
}
You cannot set value "a" of undefined, because "foo[i]" is undefined. You never defined foo[i]. Did you mean foos[i], maybe?
Also, as others have said, your function foobar is going to use the same value of i for every object you create. You should create a new closure with i, which will allow you to define a local variable i that can be different for each interior function, as so:
for(var i=0; i<100; i++) {
(function(i) {
// now do stuff with i
})(i);
}
The value of i in the execution of foobar is the one at the end of your loop (100). Because the loop block is a closure.
You'd better store the value you want. For example :
for(var i = 0; i < 100; i++) {
foos[i] = new Foo(i*10);
bars[i] = someObject.createBar({
x : 0,
y : 0,
i : i,
foobar: function() {
foos[i].a = true;
}
});
}
Or use a intermediate closure in your loop to enclose i :
for(var i = 0; i < 100; i++) {
(function(i){
foos[i] = new Foo(i*10);
bars[i] = someObject.createBar({
x : 0,
y : 0,
foobar: function() {
foos[i].a = true;
}
});
})(i);
}
The first solution is more explicit, the second one is maybe now more usual.