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);
}
...
Related
Need to add for a banner three clickTags which have names like clickTag1, clickTag2,clickTag3. Now the code looks like this:
for(var i = 1; i <= 3; i++) {
document.getElementById('Destination_cta_' + i).addEventListener('click', function() {
window.open(clickTag2, '_blank'); //here I want clickTag look like clickTag + i, but its not working.
})
}
So the question is how to loop var names so I wont need to put it manually, like it is now.
The cleanest way to solve this problem would be to use an Array.
[, clickTag1, clickTag2, clickTag3].forEach(function(e, i) {
document.getElementById('Destination_cta_' + i).addEventListener('click', function() {
window.open(e, '_blank');
})
})
An alternative method: if your clickTags are global variables, you could always access them as global properties of the window object:
for(var i = 1; i <= 3; i++) (function (i) {
document.getElementById('Destination_cta_' + i).addEventListener('click', function() {
window.open(window['clickTag' + i], '_blank')
})
)(i)
The additional wrapping function fixes the closure bug mentioned in the comments above.
You want to use an array for this. An array is an indexed list of values.
var clickTags = ["","www.nba.com","www.nhl.com","www.nfl.com"];
for(var i = 1; i <= 3; i++) {
document.getElementById('Destination_cta_' + i).addEventListener('click', function() {
window.open(clickTags[i], '_blank'); //here I want clickTag look like clickTag + i, but its not working.
})
}
Notice since you are starting your loop at 1 instead of 0, i've added a blank entry for index 0 of the clickTags array.
Why it is not currently working the way you intend :
for (var i = 1; i <= 3; i++) {
// During your first loop there is a local variable `i` whose value is 1
document.getElementById('Destination_cta_' + i)
// Here you pass an anonymous function as the second argument to addEventListener
// This creates a closure, which means the function's context includes variables
// that were in scope when it was created. Right now we have the `for` loop's variable
// `i` in the current scope, so the function keeps a *reference* to that variable.
.addEventListener('click', function() {
// When this get executed in the future, the function has to know about the variable `i`,
// and thankfully there is a reference to it in this function's closure. But remember that
// the for loop executed 3 times, using that same variable. That means that every function
// was created with a closure that is keeping a reference to the same variable, whose final
// value after the loop finished, was 4.
window.alert('clickTag' + i); // Will always alert 'clickTag4' no matter which is clicked
})
}
<div id="Destination_cta_1">1</div>
<div id="Destination_cta_2">2</div>
<div id="Destination_cta_3">3</div>
How to solve this problem ?
Make sure each addEventListener call gets a function with the correct value in a closure of its own. The way to do this is to use an immediately invoked function expression to which you pass in the value you want :
for (var i = 1; i <= 3; i++) {
var element = document.getElementById('Destination_cta_' + i)
element.addEventListener('click', (function(index) {
// This function is now a closure with a reference to index
return function() {
window.alert('clickTag' + index);
}
})(i)) // calling the anonymous function with the current value of `i` binds that value
// to the function's scope
}
<div id="Destination_cta_1">1</div>
<div id="Destination_cta_2">2</div>
<div id="Destination_cta_3">3</div>
My case:
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++)
{
tds[i].onclick = function()
{
alert(i);
};
}
Expected outcome: Alert the number of TD.
However if there are 6 TDs, the returned value will always be the last value of "i". (6)
How could i make the "i" value to remain at it's value when added to the function?
Thanks!
http://jsfiddle.net/nuKEK/11/
You need to make a closure to capture the i value. Something like this
function createFunction(i){
return function(){
alert(i);
};
}
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++){
tds[i].onclick = createFunction(i);
}
DEMO: http://jsfiddle.net/nuKEK/12/
You can pass i to another function in order to get its value rather than a reference to it. In javascript, numbers are passed by value.
tds[i].onclick = (function(x) {
return function() {
alert(x); // alerting x, i's value
};
})(i); // passing i as parameter x
If that self-executing anonymous function looks a little hairy in the context of your loop, you could try Array.prototype.forEach() instead:
[].forEach.call(document.getElementsByTagName("td"), function(td, i) {
td.onclick = function() {
alert(i);
};
});
[edit] Have a look at these options and their performance.
This is one of the common mistakes in Javascript in that it is not block scoped like most languages. It is function-scoped.
You need to create a closure around the onclick to achieve this.
for (i = 0; i < tds.length; i++) {
(function (index) {
tds[index].onclick = function() {
alert(index);
};
})(i);
}
var tds = document.getElementsByTagName("td");
for(i=0;i<tds.length;i++)
{
addClick(tds, i);
}
function addClick(where, i) {
where[i].onclick = function()
{
alert(i);
};
}
You will have to force the value into a scope that will still exist when the callback is fired.
for(i=0;i<tds.length;i++){
(function(){
var _i = i;
tds[i].onclick = function(){
alert(_i);
};
})();
}
Otherwise, the value for i will always be the last index
I have a DownDownButton that I've populated from an array containing project names and ids. The list shows the project name, but I'd like to get the project id. The variable "projects" looks like this:
[Object { name="Project A", id="1325"}, Object { name="Project B", id="5241"}, Object { name="Project C", id="3224"}]
This code creates the MenuItem for the button correctly, but how do I set the variable projId in the onClick event?
for (i = 0; i < projects.length; i++) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = ?;
}
}));
}
I've tried using "projId= projects[i].id;", but that gives me an error since i is now 3. What's the correct syntax to do this?
-- Edit --
This is how I got it to work using both cookie's and Merrick's answers.
for (i = 0; i < projects.length; i++)
(function (x) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = projects[x].id;
}
}));
} (i));
Since, the onClick callback is async and javascript is functionally scoped i will be hoisted and by the time the click event happens i will be the last value of i. To maintain the scope you can simply leverage a IIFE to properly scope i.
// Block scoping will all lazily evaluate to 10
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// Block scoping will capture the current value for each function invocation
for (var i = 0; i < 10; i++) (function(i) {
setTimeout(function() {
console.log(i);
}, 100);
})(i)
Here is an example: http://jsbin.com/eyeqiy/1/edit
I don't know if it is the correct/best way to do it, but you could create a closure to give each iteration a new variable scope like so:
for (i = 0; i < projects.length; i++) {
(function(x) {
menuProjects.addChild(new MenuItem({
label: projects[i].name,
onClick: function () {
projId = x;
}
}));
}(i));
}
This creates an anonymous function (with its own scope) that is immediatly evaluated.
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.
for (var i = 0; i < somearray.length; i++)
{
myclass.foo({'arg1':somearray[i][0]}, function()
{
console.log(somearray[i][0]);
});
}
How do I pass somearray or one of its indexes into the anonymous function ?
somearray is already in the global scope, but I still get somearray[i] is undefined
The i in the anonymous function captures the variable i, not its value. By the end of the loop, i is equal to somearray.length, so when you invoke the function it tries to access an non-existing element array.
You can fix this by making a function-constructing function that captures the variable's value:
function makeFunc(j) { return function() { console.log(somearray[j][0]); } }
for (var i = 0; i < somearray.length; i++)
{
myclass.foo({'arg1':somearray[i][0]}, makeFunc(i));
}
makeFunc's argument could have been named i, but I called it j to show that it's a different variable than the one used in the loop.
How about a closure:
for (var i = 0; i < somearray.length; i++) {
var val = somearray[i][0];
myclass.foo({'arg1': val}, function(v) {
return function() {console.log(v) };
}(val) );
}
for (var i = 0; i < somearray.length; i++)
{
myclass.foo({'arg1':somearray[i][0]}, function(somearray)
{
console.log(somearray[i][0]);
});
}
And then in method foo call anonymous function with param.
You can pass variables values to annoymous function by using callback,
something like
myclass.foo(function(variable){
return function(){
console.log(variable);
}
})(variableValue);
);
check this post: https://shahpritesh.wordpress.com/2013/09/06/javascript-function-in-loop-passing-dynamic-variable-value/
All the functions/methods can be used as callbacks only. When you call the callback function you pass variables to it.
var myclass = {
foo: function(params, callback){
// do some stuff
callback(variable1, variable1, variableN);
}
}