JavaScript scoping with closure: help me understand - javascript

Running the following code:
for (var i=0; i<3; i++) {
setTimeout( function() { console.log(i); } , 500 );
}
Outputs "3" three times. It's outputting the final value of i as opposed to the value of i when the inner function is created.
If I want the output to be 1, 2, and 3, how would I write this code? How can I get it to use the value of i at the time the function is defined as opposed to its final value?

for (var i=0; i<3; i++) {
setTimeout( function(val) { return function() { console.log(val); } }(i), 500 );
}
So, at setTimeout time (at the time we define the function for setTimeout), we're calling the anonymous function taking val as a parameter. This creates a closure for each function call, storing the value of val within the scope of the function we just called. I used a self-invoking function, which creates an immediate closure.
In the code you provided, the code creates a closure, but for the larger scope of the entirety of the code, so i is local to the whole code, meaning that at run-time, the anonymous function will use the variable i that the rest of the code uses.

function f(i){
return function(){console.log(i);};
}
for (var i=0; i<3; i++) {
setTimeout(
f(i)
, 500 );
}

The modern alternative to an explicit closure (which can get a bit hairy to read when you've got a double-wrapped function) is Function#bind. Once you've hacked in support for browsers that don't do ECMAScript Fifth Edition yet, you can say:
for (var i=0; i<3; i++) {
setTimeout(function(i) { console.log(i); }.bind(window, i), 500);
}
the window is the value that this will be inside the function (you don't need a this here, so we just use the default global object). In the case where you're just calling another function/method, like here with console.log, you can use that to excise the function expression completely:
for (var i=0; i<3; i++) {
setTimeout(console.log.bind(console, i), 500);
}

alternative:
for (var i=0; i<3; i++) {
(function(val){
setTimeout(function() {
console.log(val);
},500)
}(i));
}

Related

Binding a variable at time of passing callback [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
How do I bind (or pass a value) to a callback at the time the callback function is passed to the caller?
For instance:
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(){
console.log("Iteration number ", i);
}.bind(i), i*1000);
}
}
callbackTest();
Results in:
$ node callback-with-bind.js
Iteration number 3
Iteration number 3
Iteration number 3
I would expect the binding to happen at the time the callback is passed to the caller. But no.
How do I do it?
In my actual application, the caller passes another param to my callback, so I can't simply pass i as a param.
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(myParam){
console.log("Iteration number ", i);
}.bind(i), i*1000);
}
}
callbackTest();
bind, when provided a single parameter, sets the function's this - in your example, the i inside the function is not being bound or altered at all - it's still just using the (global) i. You should use bind's second parameter, which assigns to the function's first argument, and give your function an appropriate first argument so that it can be used:
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(internalI){
console.log("Iteration number ", internalI);
}.bind(null, i), i*1000);
}
}
callbackTest();
You could also use no arguments at all and continue using bind(i), but then you would have to replace i with this inside your function, which generally isn't the sort of thing that a this should refer to:
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(){
console.log("Iteration number " + this);
}.bind(i), i*1000);
}
}
callbackTest();
Or, you can just declare i with let, which has block scope, not global scope or function scope:
function callbackTest() {
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log("Iteration number ", i);
}, i * 1000);
}
}
callbackTest();

Javascript unwanted closure behavior [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
I was trying to code something similar to this :
var funcs = [];
for (var i=0; i<5 ; ++i) {
funcs[i]=function() {
alert(i);
};
}
Obviously, calling funcs[0] won't alert 0 as expected, simply because the variable i is captured by the anonymous function, and calling any of funcs[0..4] will alert '4' (the value of i after the last iteration and shared by all created functions).
The first work around that comes to my mind is using some kind of function generator :
var funcs = [];
for (var i=0; i<5 ; ++i) {
funcs[i]=(function(cap) {
return function() {alert(cap)};
})(i);
}
This do the trick, but seems really puzzling and hard to read. Is there any better way to get the intended behavior without using a function wrapper?
The .bind function allows you to pre-bind additional parameters to a bound function:
var funcs = [];
for (var i=0; i<5 ; ++i) {
funcs[i]=function(i) {
alert(i);
}.bind(this, i);
}
This is an ES5 function, so should work on IE9+, Chrome, Safari, Firefox:
You should write it as simple as you can. I think it's easy to understand, but hard to read. So one way to simplify it is to use Nested coding style. I don't think it can't be simpler than as it is now.
I'd suggest this way:
var funcs = [];
for (var i = 0; i < 5; ++i) {
funcs[i] = (
function (cap) {
return function () { alert(cap) };
}
)(i);
}
IMHO, named functions are often superior, for both performance and readability reasons. Why not do it like this:
function foo (cap) {
return function () { alert(cap) };
}
var funcs = [];
for (var i=0; i<5 ; ++i) {
funcs[i]=foo(i);
}
Try this:
var funcs = [0, 1, 2, 3, 4].map(function(i) {
return function() {alert(i);};
});
Note: map isn't suppoted by IE8 and older, but there's a common polyfill for it.
When you don't want to embed your code with those anonymous functions, a solution is to define (named) class embedding both the state (i) and the function (in the prototype). That's more LOC but sometimes more readable :
var funcs = [];
function MyFunc(i) {
this.i=i;
}
MyFunc.prototype.doIt = function(){
alert(this.i);
};
for (var i=0; i<5 ; ++i) {
funcs[i]=new MyFunc(i);
}
funcs[2].doIt();​

Scope and timing problems with for loops and setTimeout.

This should be easy but my head seems to be frazzled just now.
The result I'm aiming for is 0,1,2,0,1,2
My code so far.... here is a fiddle to play with.
function delayedLoad(page){
console.log(page);
};
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout(function(){
delayedLoad(i);
},3000*i);
}
};
$(document).ready(function(){
// Load the content for the first time.
loadContent();
});
This is a very common issue people hit.
The trouble is that the function you're passing to setTimeout in each iteration is referencing the same i variable.
JavaScript does not have block scope, only function scope. So to create a new scope that will retain the i value you want, you need to invoke a function inside the loop, passing it i.
Example: http://jsfiddle.net/GNwhR/3/
function set_up_timeout( j ) {
setTimeout(function(){
delayedLoad( j );
},3000*j);
}
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
set_up_timeout( i );
}
};
This places your setTimeout call in another function which is invoked and passed i in each iteration. This creates a new variable scope where j in the function references the proper value.
Another approach is to have the invoked function return a function:
Example: http://jsfiddle.net/GNwhR/4/
function set_up_callback( j ) {
return function(){
delayedLoad( j );
};
}
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout( set_up_callback( i ), 3000*i );
}
};
This one calls set_up_callback, which returns a function that references j.
Finally, another common approach is to use an IIFE (immediately invoked function expression) to eliminate the named function. This is less clear IMO:
Example: http://jsfiddle.net/GNwhR/5/
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log(i);
setTimeout((function( j ){
return function() {
delayedLoad( j );
};
})( i ),3000*i);
}
};
This is basically the same as the previous example in that it returns a function. The main difference is that the function that returns the function is created and invoked inside the loop itself.
All of these illustrate the same concept, that you need to scope a variable if you wish to asynchronously reference it later and have it retain its expected value.
patrick's explanation is pretty well right on. Here's a bit of code to help you get around it.
function delayedLoad(page){
console.log(page);
};
function loadContent(){
var i,
len = 3
for (i = 0; i < len; i++) {
console.log('in loop', i);
setTimeout($.proxy(function(){
delayedLoad(this.page);
}, {page: i}),3000*i);
}
};
$(document).ready(function(){
// Load the content for the first time.
loadContent();
});
jQuery's proxy just resets what 'this' is pointing to in a function.. so, you can replace passing in a number to just resetting the scope :) (if that makes any sense.. long day)

Refer back to old iterator value in click() function for javascript/jQuery (closure question)

I'm trying to get the "click()" function to display the value of 'i' at the time I passed in the function. But its referring back to the value of 'i' after it finished. I'm drawing a blank on how to get the function to refer to the value of 'i' when I first passed the function in.
for( var i=0; i<10; i++){
var ts = $('#<span></span>').clone().click(function(){
alert(i);
});
}
NOTE:
The '#' shouldn't be there, neither should the '.clone()'
Something like this will work:
for(var i=0; i<10; i++){
(function(j) {
var ts = $('<span></span>').click(function(){
alert(j);
});
})(i);
}
You can give it a try here. Though, your creation is a bit off, I'm not sure why you'd want to create a new element just to clone it, and there's an extra # in there....I removed both of these above, but it doesn't affect the solution of an inner function.
You need to move the body of the loop to a separate function that takes i as a parameter.
You can use a normal function, like this:
for(var i=0; i<10; i++) {
makeCopy(i);
}
function makeCopy(i) {
var ts = $('#<span></span>').clone().click(function(){
alert(i);
});
}
You can also use an inline method, like this: (beware confusing syntax)
for(var i=0; i<10; i++) {
(function(i) { //Note i parameter
var ts = $('#<span></span>').clone().click(function(){
alert(i);
});
...
})(i); //Note i parameter
}

Use queried json data in a function

I have a code similar to this:
$.ajax({
success: function(data) {
text = '';
for (var i = 0; i< data.length; i++) {
text = text + '' + data[i].Name + "<br />";
}
$("#SomeId").html(text);
for (var i = 0; i< data.length; i++) {
$("#Data_"+i).click(function() {
alert(data[i]);
RunFunction(data[i]);
return false;
});
}
}
});
This gets an array of some data in json format, then iterates through this array generating a link for each entry. Now I want to add a function for each link that will run a function that does something with this data. The problem is that the data seems to be unavailable after the ajax success function is called (although I thought that they behave like closures). What is the best way to use the queried json data later on? (I think setting it as a global variable would do the job, but I want to avoid that, mainly because this ajax request might be called multiple times)
Thanks.
Your problem is that the i variable is shared by the callbacks.
Therefore, all of the callbacks run on the last item.
The simplest solution is to use $.each:
$.each(data, function(i) {
$("#Data_" + i).click(function() {
alert(data[i]);
RunFunction(data[i]);
return false;
});
});
This will make a separate function call for each iteration, so there will be a separate i variable (or, in this case, parameter) for each iteration.
You can use .bind() directly and passing the data:
for (var i = 0; i< data.length; i++) {
$("#Data_"+i).bind('click', {data: data[i]}, function() {
alert(event.data.data);
RunFunction(event.data.data);
return false;
});
}
I think you made a classical mistake, trying to generate functions in a loop. The variable i will have the same value for all functions but it is not even a valid array index anymore at the end of the loop.
See also JavaScript Closures for Dummies (no offense), example 5.
SLaks answer is a good one, but he failed to explain why it wasn't working.
The problem is due to scoping. Try this out:
var logger = function(x){
console.log(x);
};
for(var j = 0; j < 10; j++){
window.setTimeout(function(){
logger(j);
}, 1000);
}
That nice little function prints out nothing but...9s! That's because the reference to j is kept by the timeout, so by the time the timeout runs, j is already set to 9.
Contrast that with:
var logger = function(x){
console.log(x);
};
for(var j = 0; j < 10; j++){
// we're wrapping our call in an anonymous function
// don't do this in production code...make the function external instead
// so you don't create 10 functions
(function(k){
window.setTimeout(function(){
logger(k);
}, 1000);
})(j);
}
This version wraps the inner call in an anonymous function that takes as an argument the index. Since the scope of k is now limited to that function, the logger works as you'd expect.

Categories