How can I work around the Javascript closures? - javascript

Consider this small snippet of JavaScript:
for(var i in map.maps)
{
buttons.push($("<button>").html(i).click(function() { alert(i); }));
}
It creates one button for each of the fields in the map.maps object (It's an assoc array). I set the index as the button's text and set it to alert the index as well. Obviously one would expect all the buttons to alert it's own text when clicked, but instead all the buttons alert the text of the final index in the map.maps object when clicked.
I assume this behavior is caused by the neat way JavaScript handles closures, going back and executing functions from the closures in which they were created.
The only way I can imagine getting around this is setting the index as data on the button object and using it from the click callback. I could also mimic the map.maps indices in my buttons object and find the correct index on click using indexOf, but I prefer the former method.
What I'm looking for in answers is confirmation that I'm doing it the right way, or a suggestion as to how I should do it.

Embrace the closures, don't work around them.
for(var i in map.maps)
{
(function(i){
buttons.push($("<button>").html(i).click(function() { alert(i); }));
})(i);
}
You need to wrap the code that uses your var i so that it ends up in a separate closure and the value is kept in a local var/param for that closure.
Using a separate function like in lonesomeday's answer hides this closure behaviour a little, but is at the same time much clearer.

If you pass the changing value to another function as a parameter, the value will be locked in:
function createButton(name) {
return $("<button>").html(name).click(function() { alert(name); });
}
for (var i in map.maps) {
buttons.push(createButton(i));
}

for(var i in map.maps){
(function(i){
buttons.push($("<button>").html(i).click(function() { alert(i); }))
})(i);
}
The cause why the closure failed in your case is that it's value still updated even after the function is bound, which is in this case is the event handler. This due to the fact that closure only remember references to variables and not the actual value when they were bound.
With executed anonymous function you can enforce the correct value, this achieved by passing i to the anonymous function, so then inside the scope of anonymous function i is defined anew.

This is the most elegant way to do what you're trying to do:
var buttons = myCharts.map(function(chart,i) {
return $("<button>").html(chart).click(function(event){
alert(chart);
});
}
You need closures to code elegantly in javascript, and shouldn't work around them. Or else you can't do things like nested for loops (without terribly hacks). When you need a closure, use a closure. Don't be afraid of defining new functions inside functions.

Related

JS reference inside function and watch

I have something wrong with the following code. I can't understand what is wrong with it.
function some(){
for (var i=0;i<....;i++)
{
var oneObject;
...some logic where this object is set
oneObject.watch(property,function(id, oldval, newval){
globalFunction(oneObject,id,newval);
return newval;
});
}
}
If I have for example three cycles and set three different objects I have the following result. Three different objects (for example oneObject can be equal some={},some.foo={}, some.boo={}) are set. Every of them has its own watch handler (I change the object and the handler is called). The problem is that when globalFunction is called oneObject that is passed as argument is always equal to the last object of for loop.
I can't understand why it happers as for every new cycle I redeclare oneObject variable using var. Please, explain.
EDIT
Also I tried:
function some(){
for (var i=0;i<....;i++)
{
var oneObject;
...some logic where this object is set
oneObject.watch(property,function(id, oldval, newval){
(function(obj) {
globalFunction(obj,id,newval);
}(oneObject))
return newval;
});
}
}
Since oneObject refers to an object, changing it will also change other references to that object. You can solve this with a closure.
(function(obj) {
globalFunction(obj,id,newval);
}(oneObject))
This way, each time you call globalFunction it will receive a unique copy of oneObject.
You need to create a closure for the entire reference to oneObject:
(function(obj) {
obj.watch(property,function(id, oldval, newval){
globalFunction(obj,id,newval);
return newval;
});
}(oneObject));
(I'm curious what that return is expected to do in a callback, but that's a separate issue.)
It is a little hard to tell from the abstracted code you provided but this looks like a problem caused by using an asynchronous event-loop callback (i.e. the function in watch). What typically happens in situations like this: The main loop sets up a callback. The value changes, triggering the event that is being listened to (i.e. the watch). The callback is queued in the event-loop, which is different from the main executing loop. The callback doesn't get fired until the next open cycle, which might mean the main loop has been executing in the meantime, changing the value more.
It is a little hard to explain here, but here is a link to a wonderful video that will walk you through the details of what might be happening: https://www.youtube.com/watch?v=8aGhZQkoFbQ
I don't think that oneObject persists outside of the scope. You might try using an array of oneObjects so that your oneObject variable doesn't get re-assigned each iteration. It tends to be precarious to declare variables inside of a for loop.

Reference to each object in an array

This is undoubtedly due to my ignorance of JavaScript. I have two equisized arrays of objects and I loop through both of them simultaneously adding an action handler so that when the n:th object in the first array is hovered over, some magic occurs to the n:th object in the second array.
That's my intention.
In reality, the computer "forgets" the previous assignments and only executes the last magic declared, independently of which of the invoking objects gets hovered over. I know what the problem is but I can't think of a good way to solve it. There might be an answer to this already (seems like a common gotcha to me) but since I, obviously, can't word the problem correctly I get nada.
The code is like this.
for (index in pushpins) {
map.entities.push(pushpins[index]);
map.entities.push(infoboxes[index]);
Microsoft.Maps.Events.addHandler(pushpins[index], "mouseover", function (d) {
infoboxes[index].setOptions({ visible: true });
});
Please note that the problem most likely isn't specific to Microsoft.Maps but to the fact that JavaScript has a different scoping rules for variables. When I statically add a few instances and call them some1, some2 etc., I get the behavior intended. I believe it's index that somehow retains its value.
The best solution here is move
Microsoft.Maps.Events.addHandler(pushpins[index], "mouseover", function (d) {
infoboxes[index].setOptions({ visible: true });
});
to the other function like:
function addEvent(element, i) {
Microsoft.Maps.Events.addHandler(element[i], "mouseover", function (d) {
element[i].setOptions({ visible: true });
});
}
See: Javascript scope problem or What is the scope of variables in JavaScript?
index is not bound to each item. Your loop will attach a handler to each item, but they all refer to the same index which end up as n after your loop. Thus they all trigger with n indexes.
A common solution is to use an IIFE for each iteration which creates a local scope per iteration. That way, the handler will refer to that local index rather than the index outside the loop. Not optimal (JSHint screams "Don't create functions in loops"), but does the job.
for (index in pushpins) {
(function(index){
map.entities.push(pushpins[index]);
map.entities.push(infoboxes[index]);
Microsoft.Maps.Events.addHandler(pushpins[index], "mouseover", function (d) {
infoboxes[index].setOptions({ visible: true });
});
}(index));
}

Can value of a JavaScript variable be changed twice in the same function?

Not sure if this is considered best practice or if you should even do this but I have a small block of Javascript and I want to know if you can declare a variable, display that variable and then reassign it and display it again? Syntactically this seems correct but I would assume that this is not best practice and should be avoided?
Note: I did not write this block I just want to know if it's ok or if I should change it and use 2 variables code below:
var u1 = 'something';
if (u1.indexOf('Accept') > 0)
{
var URL = 'some URL';
document.writeln(URL);
URL = 'another URL';
document.writeln(URL);
}
Thanks in advance.
EDIT:Thanks for the answers, thought it was a bit daft. :/
Yes you can
You can change variable's value as many times as you need to. Variables are quite often reused so we save memory resources. Not in the way you've used them (because that's an example that would be better off providing constant strings directly when calling functions) but think of an everyday example where we don't even think of multiple variable value assignments. A for loop:
for (var i = 0; i < 100; i++)
{
...
}
In this loop variable i gets assigned a new value 101 times. This is a rather obvious example, where we don't think of this at all, but other than that, we could have a set of loops and reuse the same variable more explicitly and assign it a value lots of times like:
var counter = 0;
for(var item = GetLinkedListFirstItem(); item != null; item = item.Next)
{
counter++;
}
// other code...
counter = 0;
while (counter < 10 || someOtherCondition)
{
// do something else
}
This may be a much better example of explicit variable reusability where its value gets changed lots of times and for different purposes.
Variable naming
Variable reuse is sometimes unwanted/undesired. And that's when we have a meaningful variable name like isUserLoggedIn. It's hard to reuse such variable for other purposes because it would make code unmaintainable.
Variables that are usually reused may hence be iterators (ie. i) or generally named variables without too much meaning. Or variables with more universal name (ie. finished) which can be reused in different contexts that can be associated with such variable name.
Asynchronous code
There are certain situations where you may have problems even though looking at code may seem perfectly fine. And that's when you use async functions which is frequently the case when using Ajax calls or time-deferred calls (ie. setTimeout). Consider the following code:
var loaded = false;
$.ajax({
url: "...",
type: "POST",
success: function(){
loaded = true;
}
});
if (loaded === true)
{
// do something important
}
// ok loaded not used any more, so we can reuse it
// we can easily change its type from number to string or anything else
loaded = "Peter loaded his gun";
This code has a bug, because important code won't be executed. Ever! This is quite a frequent misconception by unsavvy developers not understanding asynchronism.
Hint: When code issues an Ajax call it doesn't wait for a response but rather continues execution and executes if statement. Even though Ajax call would respond in 0time ticks, success function wouldn't execute until this currently running code wouldn't finish execution. That's how Javascript works. Queued code execution. In the end when Ajax async code would execute it would eventually overwrite the string that was stored in the variable.
Why not? Of course, it's normal to change variable value as much times as you want. That's actually reason why it's called "variable", not "constant" :)
I'd say it's perfectly fine to do so.
However, keep in mind that it can cause problems with asynchronous code. Take the following example for instance, where async accepts a callback that runs some time later:
var a = 123;
async(function() {
alert(a); // alerts 456, because `a` was set to 456
// *before* this callback was run.
// Because there is only one `a`, that variable
// has been overridden
});
a = 456;
async(function() {
alert(a); // alerts 456
});
Yes it is possible, and in this case there is no point in creating a new variable. However, if you have a lot of code reassigning a variable later could definitely be confusing especially if at first it's an object then later it is a string.
Variables can be reassigned in JavaScript. Whether they should or not is a question of style and context.
I normally prefer to re-use variables rather than create new ones

Using Firebug and jsfiddle.net to test a function

Im a newbie programmer who got the function below from Stoyan Stefanovs object oriented JavaScript Book. He says that if you call next three times, it will output "a" and "b" and then "c". When I tried it in firebug console, it kept giving me "a", so that`s one question (a) i.e. is there something about firebug that would explain my result?
Next, I tried to run it in jsfiddle.net but it won`t output anything. http://jsfiddle.net/mjmitche/SkSMm/
Im sure Im doing something wrong, but what? Please explain if you can. Note, I did next(); and got A, and then I did next(); again and got 'a' and next(); again and got 'a'. In other words, the counter didnt change or didnt remember.
function setup(x) {
var i = 0;
return function () {
return x[i++];
};
}
var next = setup(['a','b','c']);
next();
Here is the jsfiddle link to show it works:
http://jsfiddle.net/ZnZTk/
JsFiddle is not like the console, it doesn't have a window where it will output return values. The result of the code is a web page, that is shown at the lower right.
You can use the alert method to show the values:
alert(next());
http://jsfiddle.net/SkSMm/4/
As you see, calling next three times will actually output the three values in the array. The setup function returns a delegate to the anonumous function that is created in the function. As that anonymous function uses variables outside itself, but which are local to the surrounding function, a closure is created for the function. The closure will contain the i and x variables. As the closure belongs to the delegate, it will survive from one function call to the next, and retain the values of it's variables.
You could do a similar thing just using global variables:
var x = ['a','b','c'];
var i = 0;
function next() {
return x[i++];
}
alert(next());
alert(next());
alert(next());
As the variables are declared outside the function, they will survive between the function calls.
The drawback of using global variables is that one script easily clashes with another if the variables are not given very unique names. If you use a closure, there is no risk of the variables of one script to conflict with variables of another script.
You did it wrong:
And jsfiddle: http://jsfiddle.net/ZHgW2/
Here's a neat demo that takes advantage of an imported say function and relies on a button:
http://jsfiddle.net/entropo/wxTqR/
This is a great way to test your scripts without relying on the log or alerts.
The say function is from jQuery in Action. Excerpt:
Within this function, we employ the services of a small utility function, say() C,
that we use to emit text messages to a dynamically created element on the page
that we’ll call the “console.” This function is declared within the imported support
script file (jqia2.support.js), and will save us the trouble of using annoying and disruptive alerts to indicate when things happen on our page. We’ll be using this handy function in many of the examples throughout the remainder of the book.

MOOTOOLS variable scope

I'm using mootools:
I can't figure out how to use a variable when using an addEvent.
I want to use a for next loop to set values in a loop:
for (x=0;x<num;x++){
var onclickText = 'function (){onclick="addPageMoveEvent('+x+'"); }';
$('pageNum'+x).addEvent('click', onclickText);
}
>
I've search forums but not found any help.
Any help would be great.
Thanks
The addEvent method in MooTools accepts two arguments:
myElement.addEvent(type, fn);
Arguments:
type - (string) The event name to monitor ('click', 'load', etc) without the prefix 'on'.
fn - (function) The function to execute.
It does not take a string and passing a string such as "myFunction()" or "function() { myFunction(); }" will not work.
Since you are inside a loop, and the variable x will share the environment, you need to wrap its value inside another closure. One way is to use an additional closure:
$("pagenum" + x).addEvent("click", (function(value) {
return function() { addPageMoveEvent(value); }
})(x));
See all questions on StackOverflow regarding this particular problem of creating closures within loops.
Also worth checking out is this MDC article - Creating closures in loops: A common mistake
Warning: this first example will not work! Read on for an explanation.
You are confusing onclick HTML syntax with the MooTools addEvent. Try
for (var x=0;x<num;x++){
$('pageNum'+x).addEvent('click', 'addPageMoveEvent('+x+');');
}
This is simpler and cleaner, but might still not do what you want. This code will call the function addPageMoveEvent every time the link is clicked... is that what you want?
Since MooTools doesn't allow the above method, you must use the following:
A programmatically more interesting and less hazardous way to do the same would be:
factory = function (x) { return function() { addPageMoveEvent(x); }; };
for (var x=0;x<num;x++){
$('pageNum'+x).addEvent('click', factory(x));
}
This uses a factory for creating closures that hold your values of x... rather complex code, but it's the purist way. It also avoids using the scary eval that occurs because you feed addEvent a string. (It seems that MooTools doesn't like the other option anyway.)
That a use case for mootools pass method.
for (x=0;x<num;x++){
$('pageNum'+x).addEvent('click', addPageMoveEvent.pass(x));
}
Pass internally creates a closure that holds x in the his scope, so when the click event is fired it has the right value cause its not the same from the for loop.

Categories