javascript: localise variables in block [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I try to understand variable scope in Javascript. Here is what I am doing:
<script>
for (var i = 0; i < 3; i++) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}
</script>
The output is always 3, and I understand that's because i has been retained by reference. How do I localise i so it can log incremented value?
Thanks!
update
Thanks guys for quick and decent responses. the solutions are indeed of help!
Initially, I was trying a similar approach to #GrailsGuy, here it is:
<script>
for (var i = 1; i <= 3; i++) {
document.getElementById(i).onclick = function() {
console.log(logCall(i));
}
}
function logCall(i) {
return i;
}
</script>
But it looks like i isn't being localised. Cannot figure out why!

Create a new scope
for (var i = 0; i < 3; i++) {
(function(i) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}(i));
}

In Javascript, scope is not created by brackets (contrary to the C-like syntax). Scope is however, created in functions, so one way is to extract the call into a function (updated):
<script>
for (var i = 0; i < 3; i++) {
logCall(i);
}
function logCall(i) {
document.getElementById(i).onclick = function() {
console.log(i);
}
}
</script>
This has the added benefit of making the code a bit more reusable.

Related

Unable to get correct value on click of elements added to the DOM [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
Why doesn't the code below print it's corresponding value when clicked?
Instead, it prints 5?
var recipeDiv = document.createElement("div");
var recipeUL = document.createElement("ul");
for (var i = 0; i < 5; ++i) {
var listNode = document.createElement("li");
listNode.innerHTML = i;
listNode.onclick = function() { alert(i); }
recipeUL.appendChild(listNode);
}
recipeDiv.appendChild(recipeUL);
addNodeToDOM(document.body, recipeDiv, 'recipe');
function addNodeToDOM(element, node, id) {
var div = document.createElement('div');
div.id = id;
div.appendChild(node);
element.appendChild(div);
}
I was able to reproduce the bug here: jsfiddle
But basically, I'm not sure if this is the convention for adding elements correctly to the DOM. If so, how come whenever I click on the list elements, it doesn't show
When, onclick handler executes, the value of i will always show 5 as the for loop ends at 5th iteration,
Use this.innerHTML,
listNode.onclick = function() { alert(this.innerHTML); }
Updated Fiddle
Another simple solution is to have a wrapper function.
for (var i = 0; i < 5; ++i) {
(function(i){
var listNode = document.createElement("li");
listNode.innerHTML = i;
listNode.onclick = function() { alert(i); }
recipeUL.appendChild(listNode);
})(i);
}
This way you can always rely on the iterator variable i.
This is where closure concept plays its role. Try modifying listNode.onclick = function() { alert(i); } line of code with this:
listNode.onclick = (function(index) { return function(){alert(index);} })(i);
I have updated your fiddle..
FIDDLE

How to reference a looped element inside a jQuery event [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
Script:
for (var i = 0; i < products.length; i++) {
for (var j = 0; j < products.length; j++) {
$(document).on('change', $(products[i][j].checkbox) , function () {
products[i][j].checked ? products[i][j].checked = false : products[i][j].checked = true;
};
}
}
How I cant get products[i][j] in my event function?
Use an anonymous function that's invoked immediately to create a new scope.
This way the anonymous function of the change event becomes a closure.
for (var i = 0; i < products.length; i++) {
for (var j = 0; j < products.length; j++) {
//anonymous function (outer function)
(function() {
var product = products[i][j];
//now the anonymous function of the change event will be called
//within this new scope, with each unique product in it
$(document).on('change', function () {
//this is function is now a closure with scope of the outer function
product.checked ? product.checked = false : product.checked = true;
};
})();
}
}
The scope of the product variable will fall inside the anonymous function closure and each change event will have a unique product value associated.
Fiddle: http://jsfiddle.net/mattdlockyer/t2h50aun/1/
Closures: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
Immediately Invoked Functions: http://en.wikipedia.org/wiki/Immediately-invoked_function_expression
There seems to be another issue with your code, why are you checking document for change?

weird when using onload in a for-loop [duplicate]

This question already has answers here:
Javascript infamous Loop issue? [duplicate]
(5 answers)
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
I am making a game using JavaScript and HTML canvas. The 'onload' works perfectly without a for-loop. But 'onload' become buggy and unreasonable in a for-loop. However, I came up with a simple workaround to skip that problem. But I am still curious why. Here comes the codes and how is the workaround:
I have created an object:
function Monster() {
this.image = new Image();
this.ready = false;
}
and created to a few (let's say two) instances:
var monster = [];
for(var a = 0; a < 2; a++) {
monster.push(new Monster());
}
When I tried to initialize these objects, it's gonna fail:
for(n = 0; n < monster.length; n++) { //assume length is 2
monster[n].image.onload = function() {
monster[n].ready = true; /* Problem raise up here, n has the same value as monster.length.
If length is 2, this line gonna run 2 times */
};
monster[n].image.src = 'images/m1.png';
}
However, this problem could be solved easily by creating a function:
for(n = 0; n < monster.length; n++) {
makeMonster(n);
}
And:
var makeMonster = function(n) {
monster[n].image.onload = function() {
monster[n].ready = true;
};
monster[n].image.src = 'images/m1.png';
};
The question is, why?
The onload function is async, so by the time it fires the loop has completed and the value of n is whatever it was set to last.
You have to lock the value of the n variable in a new scope with a closure, creating a new function also creates such a scope, or like this
for(n = 0; n < monster.length; n++) {
(function(k) {
monster[k].image.onload = function() {
monster[k].ready = true;
}
monster[k].image.src = 'images/m1.png';
})(n);
}

how to return two functions for onclick event [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do I pass the value (not the reference) of a JS variable to a function?
i have several links (var a[]) with an onclick event on them. this works well so far by using one function:
function setCurrentImage(x) {
return function() {
alert("Image: "+x);
return false;
}
}
for(var i = 0; i < a.length; i++) {
a[i].onclick = setCurrentImage(i);
}
the thing is, i need two functions. and this code just won't work:
function setCurrentImage(x) {
return function() {
alert("Image: "+x);
}
}
function setCurrentNote(x) {
return function() {
alert("Note: "+x);
}
}
for(var i = 0; i < a.length; i++) {
a[i].onclick = function() {
setCurrentImage(i);
setCurrentNote(i);
return false;
}
}
what is wrong with this code? thanks in advance :)
Each of the functions that you call returns a function, but doesn't actually do anything.
You never call the functions that they return.
Once you fix that, you'll have another problem; all of your inline handlers share the same i variable.
You need to wrap that in an IIFE.
To extend SLaks's answer:
function setCurrentImage(x) {
alert("Image: "+x);
}
function setCurrentNote(x) {
alert("Note: "+x);
}
function setOnclickHandler(x) {
return function() {
setCurrentImage(x);
setCurrentNote(x);
return false;
};
}
for(var i = 0; i < a.length; i++) {
a[i].onclick = setOnclickHandler(x);
}

How to properly pass argument in loop to multiple event handlers? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
I add event handlers to multiple hrefs on my website with JS like this:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++)
{
button.addEventListener('click',function() { addTosel(i); },true);
}
}
}
But unfortunately to addTosel is passed the last i not the i from the loop. How to pass i accordingly to the object being processed in this moment?
You need to create a closure:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', function(index) {
return function () {
addTosel(index);
};
}(i), true);
}
}
This way the scope of the handler is bound to the proper context of i.
See this article for more information on this subject.
You need to bind the i variable to the function when its declared. like so
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click',(function() { addTosel(this); }).bind(i) ,true);
}
Note: I just wrote the code from memory so it may not be perfect, but it is the sulution you're needing, for reference as to the proper way, ie with cross browser shims etc look at:
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
If you're going to take the .bind approach, do it like this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', addTosel.bind(null, i), true);
}
This makes a new function with null bound as the this value since your function doesn't seem to need it, and the current i bound as the first argument.
Or make your own binder function
var _slice = Array.prototype.slice;
function _binder(func, ctx /*, arg1, argn */) {
var bound_args = _slice.call(arguments, 2);
return function() {
return func.apply(ctx, bound_args.concat(_slice.call(arguments)));
}
}
And then do this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', _binder(addTosel, null, i), true);
}

Categories

Resources