Variable scoping and event handler - javascript

Please see the jsfiddle:
http://jsfiddle.net/LsNCa/2/
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
var myDiv = $('<div>');
myDiv.click(function(e) {
alert(i); // both the two divs alert "2", not 0 and 1 as I expected
});
$('body').append(myDiv);
}
}
var myFunc = new MyFunc();
I want the divs to alert "0" and "1" respectively when I click them, but both of them alert "2".
When I click the divs and the event is triggered, how and where do the handler find the value of the variable i?
I'm aware that adding a closure achieves my goal. But why?

function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
(function(j) {
var myDiv = $('<div>');
myDiv.click(function(e) {
alert(j);
});
$('body').append(myDiv);
})(i);
}
}
var myFunc = new MyFunc();
The code above is how you get it work correctly. Without an closure, you always the the last value of i. What we do is to post i into the closure and let the runtime "remember" the value of that very moment.

You need a closure because all your event handler functions are referencing the same variable i. The for loop updates this, and when the loop is done the variable contains 2. Then when someone clicks on one of the DIVs, it accesses that variable.
To solve this, each event handler needs to be a closure with its own variable i that contains a snapshot of the value at the time the closure was created.

I suggest that you read this article
JavaScript hoists declarations. This means that both var statements
and function declarations will be moved to the top of their enclosing
scope.
As #Barmar said in his answer above, the variable i is being referenced by both the event handlers.
You should avoid declaring functions inside loops. Below there is some code that does what you need.
I assume that you're using jQuery.
function MyFunc() {
for (var i = 0; i < 2; i++) { // i= 0, 1
var myDiv = $('<div>');
$('body').append(myDiv);
}
$('div').on('click', function() {
alert($(this).index());
});
}
var myFunc = new MyFunc();

The "alert()" call happens after the for-loop completed, which means that the value of "i" will be the last value for anything after that. In order to capture individual values of "i", you must create a closure for each value by creating a new function:
function MyFunc() {
function alertFn(val) {
return function () {
alert(val);
};
}
for (var i = 0; i < 2; i++) {
var myDiv = $('<div>');
myDiv.click(alertFn(i));
$('body').append(myDiv);
}
}
var myFunc = new MyFunc();
The closure captures the value of "i" at the time it was passed into the function, allowing alert() to show the value you expect.

Related

java script last iteration set the value for all iterations

var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
var curValue = myElements[i].getAttribute('innerId')
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + curValue);
}, false);
}
when mouse over, every element, instead of showing a different value for curValue, a constant value (the last iteration value) is displayed.
what am i doing wrong here?
There is no different scope inside blocks like for in JavaScript, so when your mouseover event is triggered, it will alert the current variable value which was set in the last iteration.
You can just use this inside your callback function to get the attribute of the object which the event was triggered.
var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + this.getAttribute('innerId'));
}, false);
}
The general issue here is the closure in Javascript. This happens when using variable (in this case curValue) not defined within the callback function.
I recommend reading answers about JS closures here.

Multiple different event listeners in for loop

The code below always returns undefined. Why is this? I want the event listener to respond with the string of the index.
Thanks
var array = ["Hey", "Hi", "Hello"];
for (var i = 0; i < array.length; i++) {
var box = document.createElement("div");
box.className = "box";
box.addEventListener("click", function() {
alert(array[i]);
}, false);
}
This is asked frequently. JavaScript doesn't have block scope. Variable scope is only created when you invoke a function. So to scope your i to the current loop iteration, you need to reference it in a function invocation that also creates the handler.
// Create a function that returns a function
function createHandler(i) {
// The value of `i` is local to this variable scope
// Return your handler function, which accesses the scoped `i` variable
return function() {
alert(array[i]);
}
}
var array = ["Hey", "Hi", "Hello"];
for (var i = 0; i < array.length; i++) {
var box = document.createElement("div");
box.className = "box";
// Invoke the `createHandler`, and pass it the value that needs to be scoped.
// The returned function will use its reference to the scoped `i` value.
box.addEventListener("click", createHandler(i), false);
}
I would strongly encourage you to use named functions for this instead of the trendy inline function invocations. It's potentially more efficient, and the function name provides documentation as to the purpose of the function.
You need to wrap the click handler in a closure, to create a local copy of i:
box.addEventListener("click", (function(i) {
return function() {
alert(array[i]);
}
})(i), false);
Fiddle
The way your code is now, i ends up with a value of 3, and array[3] is of course undefined. The above creates 3 copies of i with values 0, 1, 2.
Possibly the simplest solution would be this:
box.addEventListener("click", alert.bind(window, array[i]), false);
But this won't work in IE<9.

Scope troubles in Javascript when passing an anonymous function to a named function with a local variable

Sorry about the title - I couldn't figure out a way to phrase it.
Here's the scenario:
I have a function that builds a element:
buildSelect(id,cbFunc,...)
Inside buildSelect it does this:
select.attachEvent('onchange',cbFunc);
I also have an array that goes:
var xs = ['x1','x2','x3'...];
Given all of these, I have some code that does this:
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,function(){ CallBack(xs[i],...) },...);
}
The issue is that when onchange gets fired on one of those selects it correctly goes to CallBack() but the first parameter is incorrect. For example if I change the third select I expect CallBack() to be called with xs[2] instead I get some varying things like xs[3] or something else.
If I modify it slightly to this:
for(var i = 0; i < xs.length; i++)
{
var xm = xs[i];
buildSelect(blah,function(){ CallBack(xm,...) },...);
}
I'm still getting incorrect values in CallBack(). Something tells me this is scope/closure related but I can't seem to figure out what.
I simply want the first select to call CallBack for onchange with the first parameter as xs[0], the second select with xs[1] and so on. What could I be doing wrong here?
I should clarify that xs is a global variable.
Thanks
You need to capture that xm value by closing around it in its own scope.
To do this requires a separate function call:
buildCallback( curr_xm ) {
// this function will refer to the `xm` member passed in
return function(){ CallBack(curr_xm,...) },...);
}
for(var i = 0; i < xs.length; i++)
{
var xm = xs[ i ];
buildSelect(blah,buildCallback( xm ),...);
}
Now the xm that the callback refers to is the one that you passed to buildCallback.
If you have other uses for i that need to be retained, you could send that instead:
buildCallback( curr_i ) {
// this function will refer to the `i` value passed in
return function(){ CallBack( xs[ curr_i ],...) },...);
}
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,buildCallback( i ),...);
}
The problem is indeed scope-related -- JavaScript has only function scope, not block scope or loop scope. There is only a single instance of the variables i and xm, and the value of these variables changes as the loop progresses. When the loop is done, you're left with only the last value that they held. Your anonymous functions capture the variables themselves, not their values.
To capture the actual value of a variable, you need another function where you can capture the local variable:
function makeCallback(value) {
return function() { CallBack(value, ...) };
}
Each call to makeCallback gets a new instance of the value variable and if you capture this variable, you essentially capture the value:
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,makeCallback(xs[i]),...);
}
Yes, I think a closure would help:
for(var i = 0, l = xs.length; i < l; i++)
{
buildSelect(
blah,
function(xm){
return function(){
CallBack(xm,...)
};
}(xs[i]),
...
);
}
Edit: I also optimised your for loop slightly.
Edit: I guess I'll add an explanation. What you're doing is creating an anonymous function which takes one argument (xm) and calling the function straight away (with the parenthesis right after). This anonymous function must also return your original function as an argument of buildSelect().
Apparently there is a new let keyword that does what you want:
for(var i = 0; i < xs.length; i++)
{
let xm = xs[i];
buildSelect(blah,function(){ CallBack(xm,...) },...);
}

javascript: Using the current for-loop counter-value inside a function() { }?

on a website i want to do this: (simplified)
myHandlers = new Array();
for(var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
I could set the counter as another attribute of my Handler (which would copy the current value) and use it inside my function, but I guess, there is also a way to actually copy this value, no?
When handlerFunc is called, the i inside the function refers to the i of the for loop. But that i does probably not have the same value any more.
Use a closure to bind the current value of i in the scope of an anonymous function:
handlerFunc: (function(i) { return function(bla) { /*...*/ alert(i); }; })(i)
Here an anonymous function (function(i) { … })(i) is used and called immediately. This function binds the value of i of the for loop to the local i. That i is then independent from the i of the for loop.
var myHandlers = new Array();
for (var i = 0; i < 7; i++) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc:
(function(i) {
return function(blah) {
alert(i)
}
})(i)
}))
}
Use a closure to bind the i so the value stays intact
In your example, i in the functions is the same variable as i outside the functions. As i is incremented in the loop, so is it incremented within the functions. As a result, if the functions are called after the loop has finished, they will all alert "7".
You need to create a new variable with appropriate scope and copy the value of i into it.
Something like this would create the desired effect.
...
var pushHandler = function(i) {
myHandlers.push(new Handler({
handlerName: 'myHandler'+i, // works, e.g. ->myHandler1, 2, 3 etc.
handlerFunc: function(bla) { /*...*/ alert(i); } // doesn't work,all return 7
}
}
...
for(var i = 0; i < 7; i++) {
pushHandler(i);
}
...

Why does one JavaScript closure work and the other doesn't?

There are two versions, supposedly when the user click the first link, it will alert "1", and the second link, "2", etc.:
Version 1:
click me
click me
click me
click me
click me
<script type="text/javascript">
for (i = 1; i <= 5; i++) {
document.getElementById('link' + i).onclick = (function() {
return function() {
var n = i;
alert(n);
return false;
}
})();
}
</script>
Version 2:
click me
click me
click me
click me
click me
<script type="text/javascript">
for (i = 1; i <= 5; i++) {
document.getElementById('link' + i).onclick = (function() {
var n = i;
return function() {
alert(n);
return false;
}
})();
}
</script>
Version 1 will not work. Version 2 will. I think I know the reason why, but would like to compare with other people's explanations as to why version 1 doesn't work.
Version 1 does not work because there's a common variable "i" (a global variable in this case, because you forgot var) that is shared by every "click" handler function the loop creates.
In the second version, you create a new lexical scope with the little wrapper function. That gives each "click" handler it's very own private "i".
In the second example you create a var n = i; it makes i value scoped inside the onclick function. While at the first one the onclick function still uses global value of i
I'd suggest this usage instead:
for (i = 1; i <= 5; i++) {
document.getElementById('link' + i).onclick = (function(i) {
return function() {
alert(i);
return false;
}
})(i);
}
In this case you'll get the same behaviour since i will be the local variable for onclick function as it's an argument.
First does not work because: i is the part of each closure. After 5 iterations now i is 6 due to postfix increment operator. Each time when event handler is invoked it gets the value of i from its closure scope that is always 6.
Second part works: because each closure makes a copy of i in n, n is part of each closure.

Categories

Resources