Why aren't these two bits of JavaScript equivalent? - javascript

in jquery 1.4.2, ff 3.6.6:
The following code produces three divs, which write messages to the firebug console as you would expect. However, if you uncomment out the loop and comment out the 3 lines doing it manually, it doesn't work - mousing over any of the divs results in "three" being written to the console.
Why are these two methods any different than each other? In each one you use a selector to find the element and add an event to it.
<head>
<script type="text/javascript" src="/media/js/jquery.js"></script>
<script>
$( document ).ready( function() {
$("#one").mouseenter(function(){console.log("one")})
$("#two").mouseenter(function(){console.log("two")})
$("#three").mouseenter(function(){console.log("three")})
// names=['one','two','three'];
// for (k in names){
// id=names[k]
// $("#"+id).mouseenter(function(){console.log(id)})
// }
})
</script>
</head>
<body>
<span id="one">ONE</span>
<p><span id="two">TWO</span></p>
<p><span id="three">THREE</span></p>
</body>

You would be having a very common closure problem in the for in loop.
Variables enclosed in a closure share the same single environment, so by the time the mouseenter callback is called, the loop will have run its course and the id variable will be left pointing to the value of the last element of the names array.
This can be quite a tricky topic, if you are not familiar with how closures work. You may want to check out the following article for a brief introduction:
Mozilla Dev Center: Working with Closures
You could solve this with even more closures, using a function factory:
function makeMouseEnterCallback (id) {
return function() {
console.log(id);
};
}
// ...
var id, k,
names = ['one','two','three'];
for (k = 0; k < names.length; k++) {
id = names[k];
$("#" + id).mouseenter(makeMouseEnterCallback(id));
}
You could also inline the above function factory as follows:
var id, k,
names = ['one','two','three'];
for (k = 0; k < names.length; k++) {
id = names[k];
$("#" + id).mouseenter((function (p_id) {
return function() {
console.log(p_id);
};
})(id));
}
Any yet another solution could be as #d.m suggested in another answer, enclosing each iteration in its own scope:
var k,
names = ['one','two','three'];
for (k = 0; k < names.length; k++) {
(function() {
var id = names[k];
$("#" + id).mouseenter(function () { console.log(id) });
})();
}
Although not related to this problem, it is generally recommended to avoid using a for in loop to iterate over the items of an array, as #CMS pointed out in a comment below (Further reading). In addition, explicitly terminating your statements with a semicolon is also considered a good practice in JavaScript.

This happens because when the code of the anonymous function function(){console.log(id)} is executed, the value of id is three indeed.
To can use the following trick to enclose the value on each loop iteration into the anonymous callback scope:
names=['one', 'two', 'three'];
for (var k in names) {
(function() {
var id = names[k];
$("#"+id).mouseenter(function(){ console.log(id) });
})();
}

Related

Loop and Functions

I'm having troubles gathering information about clicked eventListeners.
I have this loop which builds an array:
myButtonList = document.getElementsByTagName('a');
myAnchorList = [];
for (i=0; i < myButtonList.length;i++) {
if (myButtonList[i].getAttribute('class') == 'flagged') {
myAnchorList.push(myButtonList[i]);
}
}
For each <a> put into myAnchorList array, I also create another array storing other informations from the same tag (classe and other atrributes).
Here's where I'm struggling. I'm trying to set up an eventListener to send me back those information when those <a> are being clicked. But somehow, the fact that I create a function (for the eventListener) within a loop breaks everything.
for (i=0; i < myAnchorList.length; i++) {
myAnchorList[i].addEventListener("click", function(i){
console.log(alpha+' - '+beta[i]+" - "+charlie[i]);
});
}
My values will either be undefined or some other values which will be the same for each buttons I clicked. alpha is working well as it doesn't depend on any iteration of the loop, but not the others.
Can anybody see what I'm doing wrong here?
for (var i = 0; i < myAnchorList.length; i++) {
(function (i) { //Passes i to your function
myAnchorList[i].addEventListener("click", function () {
console.log(alpha+' - '+beta[i]+" - "+charlie[i]);
});
})(i);
}
The variable "i" in closure that you created in the loop will always retrieve the last value(myAnchorList.length - 1). You shouldn't create closure in a loop, and you can use a "factory" method to create closure instead.

Can someone please explain to me how the scope works for this array of anonymous functions?

Quite simply, I'd like to know why the call to arr0 seems to drag in the value of i instead of the one stored in the function at that position.
<script>
var arr = [];
for(var i = 0; i < 3; i++) {
//Assign anonymous functions to the array in positions 0 to 2
arr[i] = function() { console.log("function " + i); }
}
for(var i = 0; i < 3; i++) {
//The output for these function calls is correct!
arr[i]();
}
//Here I expected to output: function 0, but instead outputs: function 3 WTF!
arr[0] ();
</script>
Here's the output:
function 0
function 1
function 2
function 3
For the last call, i.e: arr[ 0 ] (); I expected the output to be "function 0", but surprisingly IT'S NOT... Could someone please care to explain why?
Thanks in advance!
Well this is a mixed bunch...
You are using the same i variable (despite "re-defining" it in the second loop, it's still the same i) that is placed in the global scope
As a result, in the second loop, each iteration alters the value of that global i, which results in the
function 0
function 1
function 2
function 3
output.
It was, by the way, absolutely not the expected result, if you used k in the second loop:
<script>
var arr = [];
for(var i = 0; i < 3; i++) {
//Assign anonymous functions to the array in positions 0 to 2
arr[i] = function() { console.log("function " + i); }
}
for(var k = 0; k < 3; k++) {
//The output for these function calls is correct!
arr[k]();
}
//Here I expected to output: function 0, but instead outputs: function 3 WTF!
arr[0] ();
</script>
That would produce:
function 3
function 3
function 3
function 3
and that is the infamous loop problem referred in the link above (in comments).
The reason is that functions, defined in your first loop (which, BTW, you should try to avoid in general case), "close" on the variables that are in the same scope as their definition - i in this case. That means that whenever value of i is changed later on, it will be reflected in that function.
The last example shows it in action - i is changed by the first for loop, so when the loop is finished - it's value is 3. All functions you defined now have the same value of i - 3.
To make the output like this:
function 0
function 1
function 2
function 0
you can do this (not that it's that great, structure wise):
var arr = [];
for(var i = 0; i < 3; i++) {
//Assign anonymous functions to the array in positions 0 to 2
arr[i] = (function(index){ return function() { console.log("function " + index); };}(i));
}
for(var k = 0; k < 3; k++) {
//The output for these function calls is correct!
arr[k]();
}
//Here I expected to output: function 0, but instead outputs: function 3 WTF!
arr[0] ();
That produces the ddesire result.
Here, you define an anonymous function:
(function(index){ ... }(i))
that is immediately invoked with i as a parameter. That parameter is referred to as index in the function body (not that it's important, even if you still called it i - it would work, since the "inner" i would shadow the "outer" one).
That function returns a function that has a different closure - on index, which is not important since index is not avialable after the immediately invoked function exists.
Another way would be using some sort of iterator - map, where supported, would do OK.
It's a common problem, after you've read the duplicate posted in the comments, here's a possible solution:
var arr = [0,1,2].map(function(i){
return function(){
console.log("function " + i);
};
});
for(var i = 0; i < 3; i++) {
arr[i]();
}
arr[0]();
By creating an isolate scope with map we avoid the problem altogether. Using underscore's _.range you could replace your loop patterns with:
_.range(0,10).map(function(index){
...
})
The question linked to in one of the comments will give you the general answer.
But I'll also specifically address what you're seeing here, because it might still be slightly confusing even after understanding the answers to the other question.
The main thing to realize here is that there is only one i variable. Even though it looks like you're declaring it twice with the var keyword, the second "declaration" is essentially ignored and treated like any ordinary assignment. You see, the for keyword does not introduce a new scope in JavaScript. So these two snippets are equivalent:
for (var i = 0; i < 3; i++) {}
And:
var i;
for (i = 0; i < 3; i++) {}
Once you realize that, and you get that the functions you create in the first loop all close over the same i, then you can understand why the second loop appears to be "correct" by your intuition: at the start of the loop you set i to 0, and then after each call you increment it. So even though all of them close over the same i, you're changing its value between calls!
And of course, for that last call, the value of i is still 3 since that's what it was at the end of the second loop and you didn't change it from that.

Using javascript closures for context in loops

Is it bad practice to make a jQuery closure simply for the purpose of retaining context?
Example:
{
testFunction: function() {
//Instantiate variables etc...
for (var i = 0; i < columns.length; i++) {
for (var j = 0; j < columns[i].length; j++) {
// create a closure so the .then callback has the correct value for x
var func = function() {
var x = new testObject(columns[i][j].Id);
// find method returns a jQuery promise
x.find().then(function() {
// use x for some logic here
});
};
func();
}
}
}
}
In this case the closure is needed so that the function for the jQuery promise has context for what x is. Otherwise the x variable is changed after the next iteration of the loop. The jQuery promise object gets the value of whatever x was in the last iteration of the loop.
I'm looking for best practices here, just wondering what to do to keep code simple, readable and efficient.
Please note any performance issues that come along with closures/not using closures. References are appreciated.
Using a closure is definitely a good practice, but you can simplify it a little:
Pass in the object directly to your closure.
Use an IIFE instead of a named function declaration.
for (var i = 0; i < columns.length; i++) {
for (var j = 0; j < columns[i].length; j++) {
(function(x) {
x.find().then(function() {
// use x for some logic here
});
}( new testObject(columns[i][j].Id) ));
}
}
Note: you could simplify this code a little by using jQuery's $.each() method, but using a regular for loop is way faster.
jQuery (or ES5) iterators usually lead to cleaner code compared to loops. Consider:
$.each(columns, function() {
$.each(this, function() {
var x = new testObject(this.Id);
x.find().then(function() {
// stuff
});
});
});
Note that the scoping problem doesn't arise here, since you get a new scope on every iteration automatically. To address the performance question, iterators are slower than loops, but in the event-driven world you hardly need to worry about that.
Unfortunately there is no a better way to solve the problem. However, you miss to pass i and j:
for (var i = 0; i < columns.length; i++) {
for (var j = 0; j < columns[i].length; j++) {
// create a closure so the .then callback has the correct value for x
var func = function(i, j) {
var x = new testObject(columns[i][j].Id);
// find method returns a jQuery promise
x.find().then(function() {
// use x for some logic here
});
};
func(i, j);
}
}
It is ok and it's actually the only way to create a context.
Unfortunately for some reasons I don't really understand there are Javascript engines that limit the number of nested levels of functions you can create to very low numbers so just try to use them only when really needed (e.g. I found that my quite powerful Galaxy S4 with Android only can handle 18 nested levels).
For hand-written code this is not an issue normally, but for generated javascript code it's quite easy to get past those untold limits.

basic jquery looping question around 'click'

Basic question, but I have been pounding my head for a bit so thought id bring it here.
html looks like this (edit, fixed the closing quotes)
<span class='deleteimage-119'>delete</span>
<span class='deleteimage-120'>delete</span>
<span class='deleteimage-121'>delete</span>
<span class='deleteimage-122'>delete</span>
<span class='deleteimage-123'>delete</span>
javascript/jquery looks like this
iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
The click functionality gets attached to each individual span, but the alert after clicking just shows the last item in the array.
You have a scoping issue. By the time the callback fires, place has the last value from the loop.
You need to create a new variable for the closure; one variable per iteration, each of which will then be "caught" by the closure and used in the callback.
It would be nice if the solution were this:
var iids = ['119','120','121','122','123']; // don't forget `var` please
for (var i=0; i<iids.length; i++) {
var place = iids[i]; // local variable?
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
Alas, Javascript has no block scope so there's still only one variable here called place, and it keeps getting updated as the loop runs.
So, you have to use a function instead:
var iids = ['119','120','121','122','123'];
function f(place) {
// NOW `place` is a local variable.
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
for (var i=0; i<iids.length; i++) {
f(iids[i]);
}
There are neater ways to employ this function approach using closures and bound variables, and the other answers cover those neater ways quite well. My answer has focused on explaining the issue.
The issue is with scopes and closure.
In JS the scope is # function level and not block level.
Try this:
var iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
var clickFn = function(a){
return function(){
alert(a);
}
}(place);
$(".deleteimage-" + place).click(clickFn );
}
This is because the click is occurring after the loop is completed, so you're alerting place = iids[iids.length - 1]. In order to achieve the result you're looking for you need to create a function closure and pass place in as a parameter:
iids = ['119', '120', '121', '122', '123'];
for (i = 0; i < iids.length; i++) {
place = iids[i];
(function(_place) {
$(".deleteimage-" + _place).click(function() {
alert(_place);
});
} (place));
}
Inside the loop, you are binding the click event to those span, but the events are fired only after the loop is complete. So, it will always show the last value.
As others have mentioned, you have a scoping issue. Unless all of those classes have a unique meaning, I'd move the place value to a data attribute and leave deleteimage as the class name. Something like:
<span class='deleteimage' data-place='119'>delete</span>
<span class='deleteimage' data-place='120'>delete</span>
<span class='deleteimage' data-place='121'>delete</span>
<span class='deleteimage' data-place='122'>delete</span>
<span class='deleteimage' data-place='123'>delete</span>
$(".deleteimage").click(function() {
var place = $(this).data("place");
alert(place);
});
If the place values aren't unique values, then this answer doesn't apply.

weird javascript problem

I'm dynamically inserting some html into the document (by using obj.innerHTML += 'some html'). In that html, there are images with 'imageId_X' ids (ie. imageId_1, imageId_2...). This works fine, but there's something wrong with the following code:
for (var n = 0; n < pConfig.images.length; n++)
{
document.getElementById('imageId_' + n).onclick = function()
{
alert(n);
}
}
There are 4 elements in the pConfig.images and alert(n) always alerts 4. Why is this happening, what am i doing wrong?
The cause of your problem is lamba expression in your code. When you define your anonymous function as onclick handler you bound it for outer variable n, which at the end of the loop is always 4 that the reason you get it always 4.
To do it the way you have planned it to be you need to do the following:
for (var n = 0; n < pConfig.images.length; n++)
{
function( n) {
document.getElementById('imageId_' + n).onclick = function()
{
alert(n);
}
}( n);
}
Hence you define separate scope for variable.
The problem is that each function you create has a reference to the same n variable - the one that is incremented. By the end of the loop, it is at 4 - and all functions you made refer to that variable with that value.
You can work around it with a closure, e.g.:
function closure(n) {
return function() {alert(n);}
}
for (var n = 0; n < pConfig.images.length; n++)
{
document.getElementById('imageId_' + n).onclick = closure(n);
}
Looks like you need a closure. See How do JavaScript closures work?

Categories