JavaScript not writing variable as number (loop) [duplicate] - javascript

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I have a javascript code like this and this always gives me a problem
for(var i=1;i<9;i++){
document.getElementById('element'+i).onclick=function(){
theFunc(i)
}
}
It selects the right element and adds the onclick. But, when I type in console document.getElementById('element1").onclick it returns theFunc(i) (not theFunc(1))
So no matter which element is clicked it will always call theFunc(9) (at the end i is 9)
What's wrong with my code?

Your event handler function has an enduring reference to i, not a copy of its value, as you've discovered.
To prevent that, have the function close over something else that won't change:
for(var i=1;i<9;i++){
document.getElementById('element'+i).onclick=makeHandler(i);
}
function makeHandler(index) {
return function() {
theFunc(index);
};
}
makeHandler creates a function that closes over index, which is a copy of the value of i, and so doesn't change as the loop continues. Each event handler gets its own index.
That said, creating a bunch of event handler functions that are effectively identical usually means you can redesign a bit and use just one handler function. In this case, for instance, you could do this:
for(var i=1;i<9;i++){
document.getElementById('element'+i).onclick=theHandler;
}
function theHandler() {
func(parseInt(this.id.replace(/\D/g, ''));
}
...which grabs the value to use from the id of the element.
Another approach is delegation, where you actually hook the click event on an ancestor element (one that all of these elementX's have in common), and then when the click occurs, look at event.target and its ancestors to see what you should do.

TJ Crowder's answer is the best way around your problem. This "problem" you're experiencing in your closure is by design in many languages, and is referred to as scope.
Here's a good explanation of different scopes in JavaScript (including closures) and how to use them.
http://robertnyman.com/2008/10/09/explaining-javascript-scope-and-closures/

When you say theFunc(i) you are creating a closure around i, such that every function call refers to the same variable. You need to wrap the function inside an outer closure to ensure each function call is working with a unique variable:
for(var i=1;i<9;i++){
(function(i){
document.getElementById('element'+i).onclick=function(){
theFunc(i);
}
})(i);
}

Related

Why is it not encouraged to create functions within a loop in JavaScript? [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I am an absolute newbie, and I just read this in JavaScript: The Good Parts.
In the chapter talking about scope, it says "It is important to understand that the inner function has access to the actual variables of the outer functions and not copies in order to avoid the following problem." And then the two following examples look like this:
//BAD EXAMPLE
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (e) {
alert(i);
};
}
};
//END BAD EXAMPLE
var add_the_handlers = function (nodes) {
var helper = function (i) {
return function (e) {
alert(i);
};
};
var i;
for (i = 0; i < nodes.length; i += 1) {
modes[i].onclick = helper(i);
}
};
According to the author the second example is better because it doesn't use a loop inside the function, otherwise it could be wasteful computationally. But I am at loss and don't know what to do with them. How do I put his theory in real application? Can anyone illustrate these two examples combine HTML?
The problem is with closure. The inner functions have access to the variable i defined outside of these functions. After all iterations of the loop have been executed, the variable i will hold the value of nodes.length. So when you click on nodes[0], the alert will say nodes.length, which is not what you'd expect. (You would expect the alert to say 0.) The same holds when you click on nodes[1], nodes[2], etc. The alert for all of them will say nodes.length.
Firstly, in the bad example, a function is created for each event handler; the loop creates multiple function objects. Whereas in the second example, a single function is created and referenced from inside the loop. So you save a lot of memory.
Secondly, in the bad example, as the value of "i" runs, the function does not retain the value, and when it runs, it will always return the last value of "i". In the good example however, as "i" is passed into the function, this value is retained as the lexical environment of the function, and when it is called, it will return the correct value.
Thirdly, as mentioned by #Gary Hayes, we might want to use the function elsewhere too. So it's best to keep it independent of the loop.
You can check it with HTML working here: https://jsfiddle.net/vdrr4519/.
'multifunc' elements are inited with example with many functions, 'singlefunc'—with a single one. See, we take all the elements with a class and pass them to the function.
multifunc(document.querySelectorAll('.multifunc'));
Function runs 'for' loop and adds 'click' event listener. So the element should alert its index on click (beginning from 0). But in example with many function a wrong value is produced (because of closure, other answers also highlight the issue).
I think I should say also that it's not issue of single function/mutliple functions—it's a question of working with closures. You see, I can implement a working example WITH many closures: https://jsfiddle.net/pr7gqtdr/1/. I do basically the same thing that you do in a single-function handler, but every time call the new 'helper' function:
nodes[i].onclick = function (i) {
return function (e) {
alert(i);
};
}(i);
See, this (i) at the end is an immediate function call, so onclick gets a function with i variable set in closure.
But, the single function options is a bit better, because it's more memory efficient, I guess. Functions are objects. If you create many of them, you take more memory, in general. So, choosing from these, I'd stick with 'handler' function option.
The bad example creates a lot of event handlers; One per event. The good example create a single event handler and assigns it to all the events.
With the bad example, you've created lots of separate functions, instead of just one. That can be a lot of extra overhead and a lot of potential scope problems. These include closure issues such as an event only firing for the last item in the loop.
Additionally, the good example allows you to more easily unsubscribe the events because you have access to the original function pointer.
The good example is also just easier to read and understand. The loop is only used for creating the elements and binding their events; The handling of those events is done elsewhere.
As Soviut mentions, you are creating lots of event handlers in the bad example. Moreover, it is important to point out that the bad example functions refer to the same i variable, which means all of them will have the last value of nodes.length when they execute.
This is because a closure is created. You can read more about it in Closures.

Assigning EventListener in a loop

I use this line of code to add an event listener to my div's that are created through a forloop:
for(var i in mail){
//create div
parent.addEventListener("click",function(){read_msg(mail[i].id);},false);
//append to parent
}
This is causing the problem of mail[i].id being the last id for all of them. I've read some examples of how to solve it but i find it still very confusing.
I was suggested the solution of :
(function(){read_msg(mail[this].id)}).bind(i);
But am told this is not a great solution to use, was hoping someone could explain how you get read_msg to hold the correct value of id ? It always seems a bit messy in terms of a solution.
It is because you are using a closure variable i in your event handler function.
The variable i resides in the scope of the outer function, ie there is only one instance of the variable i. When the handler method is called and i is accessed javascript will look at the scope of the handler function first, if it does not find the variable there it will look at the parent closure scopes, then it will find the variable in the parent scope.
In the parent scope the value keep changing the value of i as the loop is executing that is why all the callback have the same value for i.
The solution here is to create a local closure
for(var i in mail){
(function(myvar){
parent.addEventListener("click",function(){read_msg(mail[myvar].id);},false);
//append to parent
})(i);
}
Here what we does is we have a Immediately Invoked Function Expression, to which we are passing the value of i as parameter myvar. So each iteration in the loop will create a independent closure.
you can use Object+Array methods in most browsers to side-step the pesky loop scope "bug" in js:
function addClick(key){
//create div
parent.addEventListener("click",function(){read_msg(mail[key].id);},false);
//append to parent
}
Object.keys(mail).forEach(addClick);
since functions have scope and forEach eats functions, you don't need the extra anon wrapper when you use Array methods.
if you want to go all out new JS hotness:
function addClick(key){
parent.addEventListener("click", this.method.bind( this.elm, this.source[key].id ), false);
}
Object.keys(mail).forEach(addClick, {elm:parent, source: mail, method:read_msg });
where you invert the source object of the key to allow using objects other than "mail", elements other than "parent" to attach events upon, and methods other than "read_msg", all without having to touch the logic or use the word "return"... Basically, whatever you setup in the traditional C-style for-loop initializer, you move to this.something and re-apply the logic as the need arises.
because each anonymous function you define as an event handler with each loop iteration will share the same scope as all the others, they will all reference the same var (i) as the array address for the message you are trying to display. Because your are redefining the var i with each loop, you will always see the last message in your message array displayed on each click event because the last value assigned to i will have been the length of your "mail" array.
heres how to fix it:
var helper = function(index) {
parent.addEventListener("click", function(){read_msg(mail[index].id);},false);
}
for(var i in mail) {
helper(i);
}

closure or handler in object?

I click a button and there is a handler. I have never understood if I should use a closure, or let the handler be in a object. For example, in HTML I have,
<button id="b">Go</button>
<button id="c">Go</button>
and in JavaScript (with some jQuery),
var hdl=function(){
var hdl=function(){
foo+="foo"
console.log(foo)
},
foo=""
return hdl
}()
$("#b").click(hdl)
var obj={
bar:"",
hdl:function(){
this.bar+="bar"
console.log(this.bar)
}
}
var baz=function(){
obj.hdl()
}
$("#c").click(baz)
Both work. Or are there situations in which you can only use one of them?
An event handler is always a function or an object that implements the EventListener interface. I've never understood any reason to use an EventListener object rather than a function so I've only seen functions used, but you can use either.
If you choose a function, it's up to you whether you want a function to be a global function, an anonymously declared function or a function that is a property of an object. There is no "right" answer as it depends upon how you want to structure your code.
My event handlers are usually anonymously declared functions just because that's usually how I structure things and generally nothing more is needed. Simple is best so you should make it no more complicated than needed.
A closure is just a function body that survives longer than the simple execution of the function because some other function reference inside is still active. Whether to use a closure or not depends on your needs and again the structure of your code. Closures can be really handy ways of keeping some state without using global variables, but other times they aren't needed at all.
I think you're mixing up terms.
Closures are a natural result of JavaScript's scoping rules, not something you choose to create
The jQuery click event always takes a function. Whether that function is attached to an object—thereby making it a method—doesn't really matter. Depending on how that function is written, it may form a closure that affects you.
A closure is when a function "remembers" the variables in the context in which it's defined.
The classical closure example is something like this:
for(var i = 0; i < 10; i++)
$("#button" + i).click(function() { alert("you clicked button " + i); });
Most developers are surprised to learn that each button displays 10, which is the value of i when the outer scope ends. This happens because each of those functions declared in the loop has formed a closure over i. Those functions don't just get the value of i when they're declared, they get the actual i, in all its glory. That's why changing i after you create the function causes the created function to reflect the updated value of i
Situations like this are fixed by breaking the closure by passing i to a function, since function parameters are passed by value.
for(var i = 0; i < 10; i++)
(function(localI) {
$("#button" + i).click(function() { alert("you clicked button " + localI); });
)(i);
The only difference between your cases is that in one of them you are bundling your variables (foo) in an object. The following third example should make this point clear:
function hdl(variables){
variables.foo += variables.foo;
}
var obj = {foo: ""};
var baz = function(){
hdl(obj);
}
I don't think any of these alternatives is in anyway generally superior to the others. You should decide to use whatever solution is simpler and easier to understand and mantain depending on what your particular problem is.
For example, in the version using objects the variables are dinamically bound while in the version with closures they are statically bound. This means that the object version is more extensible (with inheritance, mixins, etc) while the closure version is more rigid (but simpler to reason about at compile time)

Variable keeps changing after function is assigned to event handler

I have a bunch of elements on a page, all of whose ID's are stored in an array called ids[].
I have initialized a third-party DOM script for each of these divs that detects when the element has been dragged. The next step is to assign a function to the onDrag event of each element.
For simplicity's sake, I'd simply like to show a popup dialog that states the ID of the element that was dragged. I am iterating through my array as follows:
for (i=0;i<ids.length;i++)
{
document.getElementById(ids[i]).onDrag = function(){alert(ids[i])}
}
This all seems well and good, but toggling the drag event of any of my elements causes a dialog to popup that states the ID of the last element in the array. In other words, it looks like the above function in my iteration is always being evaluated for the last index in my array. I feel like I am missing something very simple here but this issue is driving me nuts.
The thing you've encountered is called closure and it is an essential part of Javascript.
A closure is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).
What happens is that the anonymous function assigned to ondrag closes over it's environment including variable i. As a consequence whenever i changes outside of this function, i will contain the new value inside accordingly.
The way you can workaround this behavior in the current context is to create another scope with an additional self-executing function.
for (var i=0; i<ids.length; i++) {
document.getElementById(ids[i]).onDrag = (function(i){ // <- i is a parameter
return function() {
alert(ids[i]); // <- i will be the inner value saved from outside
};
})(i); // <- invoke immidiately with the i from outside
}
You can read more on the topic: Use Cases for JavaScript Closures by #kangax
Do the following changes,
for (...){
SetOnDrag(ids[i]);
}
function SetOnDrag(id)
{
document.getElementById(id).onDrag = function() { alert(id); };
}

Why does this Javascript object not go out of scope after $(document).ready?

I have some working Javascript that manipulates the some DOM elements. The problem is, I don't understand why it works, which is never a good thing. I am trying to learn more about object oriented javascript and javascript best practices, so the organization may seems a little strange.
Basically, I wrap two methods that manipulate the DOM inside a CSContent object. I create an instance of that object, content in $(document).ready and bind some events to the functions in content. However, I am confused as to how these functions can still be called after $(document).ready exits. Doesn't that mean that content has gone out of scope, and its functions are not available? Anyway, here is the code:
function CSContent() {
var tweetTextArea = document.getElementById('cscontent-tweet'),
tweetTextElement = document.getElementById('edit-cscontent-cs-content-tweet'),
charCountElement = document.getElementById('cscontent-tweet-charactercount');
this.toggleTweetTextarea = function () {
$(tweetTextArea).slideToggle();
};
this.updateTweetCharacterCount = function () {
var numOfCharsLeft = 140 - tweetTextElement.value.length;
if (numOfCharsLeft < 0) {
$(charCountElement).addClass('cscontent-negative-chars-left');
}
else {
$(charCountElement).removeClass('cscontent-negative-chars-left');
}
charCountElement.innerHTML = '' + numOfCharsLeft + ' characters left.';
};
}
$(document).ready(function () {
var content = new CSContent();
//If the twitter box starts out unchecked, then hide the text area
if ($('#edit-cscontent-cs-content-twitter:checked').val() === undefined) {
$('#cscontent-tweet').hide();
}
$('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);
//Seems wasteful, but we bind to keyup and keypress to fix some weird miscounting behavior when deleting characters.
$('#edit-cscontent-cs-content-tweet').keypress(content.updateTweetCharacterCount);
$('#edit-cscontent-cs-content-tweet').keyup(content.updateTweetCharacterCount);
content.updateTweetCharacterCount();
});
This, m'lord, is called a closure: the local variable content will remain in memory after $(document).ready exits. This is also a known cause of memory leaks.
In short, you bind this function to an event listener of a DOM element and then the JavaScript garbage collector knows that it should keep the local variable intact. You can't call it directly (outside of the function), unless the event is triggered. With some, you can do this ‘manually’, if you really want to call the function afterward (e.g., using element.click() to simulate a click).
I assume you wonder why the event handlers like
$('#edit-cscontent-cs-content-twitter').change(content.toggleTweetTextarea);
work?
Well you don't pass content as event handler but the function that is contained in content.toggleTweetTextarea. And this reference will still exist after content does not exist anymore. There is nothing special about it. You just assigned an object (the function) to another variable. As long as at least one reference to an object exists, the object won't be garbage collected.
Now you may ask why those functions have still access to e.g. tweetTextArea ? This is indeed a closure. When the functions are created via new CSContent(), the activation context of this function is added to the scope chain of the inner functions CSContent.toggleTweetTextarea and CSContent.updateTweetCharacterCount. So even if you don't have a reference to content anymore, the scope of this function is still contained in the scope chain of the other functions.
You won't be able to access the object contained in content anymore after ready() is finished, this indeed goes out of scope.
My brain is off today, but shouldn't you be using closures in this situation?
$('#edit-cscontent-cs-content-twitter').change(
function(){
content.toggleTweetTextarea();
}
);

Categories

Resources