javascript closure example - javascript

I was reading this website for notes on closure: blog.morrisjohns.com/javascript_closures_for_dummies.html
It had the following example:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
Their notes (correctly) point out:
The line result.push( function()
{alert(item + ' ' + list[i])} adds a
reference to an anonymous function
three times to the result array. If
you are not so familiar with anonymous
functions think of it like:
pointer = function() {alert(item + ' ' + list[i])};
result.push(pointer);
Note that when you run the example,
"item3 undefined" is alerted three
times! This is because just like
previous examples, there is only one
closure for the local variables for
buildList. When the anonymous
functions are called on the line
fnlistj; they all use the same
single closure, and they use the
current value for i and item within
that one closure (where i has a value
of 3 because the loop had completed,
and item has a value of 'item3').
Is there a "correct" way to get the "expected" behavior in javascript?
Thanks!

result.push((function (jtem, j) {
return function() { alert(jtem + ' ' + list[j]); }
})(item, i));
You create an anonymous function which introduces a new variable scope. You immediately execute that function, passing your variables, which become the new variables jtem and j in a new scope, hence the "connection" to item and i is "broken". You then return the actual function, which now uses different variables (you could keep the names i and item, I just changed them for illustrative purposes).

Yes, you assign the value of i to a another variable so that variable can be part of the closure.
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push(function(cItem, index){
return function(){ alert(cItem + ' ' + list[index]); }
}(item, i));
}
return result;
}
The reason for this is that JavaScript does not have block scope, but a lexical scope that is capture by functions. To get around this problem, you create another function, as seen above, thereby capturing the variables into a new scope that will not be affected by the loop.

Related

Implementing loop for var names for clickTags

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>

java script last iteration set the value for all iterations

var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
var curValue = myElements[i].getAttribute('innerId')
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + curValue);
}, false);
}
when mouse over, every element, instead of showing a different value for curValue, a constant value (the last iteration value) is displayed.
what am i doing wrong here?
There is no different scope inside blocks like for in JavaScript, so when your mouseover event is triggered, it will alert the current variable value which was set in the last iteration.
You can just use this inside your callback function to get the attribute of the object which the event was triggered.
var myElements = document.getElementsByName('bb1');
for (var i = 0; i < myElements.length; i++) {
myElements[i].addEventListener('mouseover', function () {
alert('Hello i am : ' + this.getAttribute('innerId'));
}, false);
}
The general issue here is the closure in Javascript. This happens when using variable (in this case curValue) not defined within the callback function.
I recommend reading answers about JS closures here.

variable closure using jQuery object notation

I have the following:
for (var i = 0; i <= 10; i += 1) {
var $page_button = $('<a>', {
html : i,
click : function () {
var index = i;
console.log(index);
return false;
}
});
$page_button.appendTo($wrapper);
}
I thought that var index would be defined separately for each iteration of the loop because it is enclosed within a function. In this case the value of index that is printed is always 10.
The link text is the correct value of i, because this is written to the DOM and is then immutable .
Why is this, and what should I change to fix my problem?
I know this is similar to lots of other questions but the behaviour of using this notation is causing a different result. I am using jQuery 1.7.2 (Can't use any newer unfortunately.)
You need to enclose that in a closure to solve the problem..
var $page_button = $('<a>', {
html : i,
click : (function (num) {
return function(){
var index = num;
console.log(index);
return false;
}
})(i)
});
A reference to i is closed up as part of the anonymous function. Note: not to its value, but a reference to i itself. When the function is run, the value is evaluated. Because the function runs after the loop has ended, the value will always be the last value of i. To pass just the value around, you do something like this:
click : (function (index) {
return function () {
console.log(index);
return false;
};
})(i)
You create an anonymous function which you execute immediately, which takes a value as argument and returns your actual function.
The variable index is defined separately for each execution of the function, but you copy the value from the variable i inside the function, so you will use the value of i as it is when the function runs, not when the function is created.
You need a function that is executed inside the loop to capture the value of the variable:
for (var i = 0; i <= 10; i += 1) {
(function(){
var index = i;
var $page_button = $('<a>', {
html : i,
click : function () {
console.log(index);
return false;
}
});
})();
$page_button.appendTo($wrapper);
}
Every handler is sharing the same i variable. Each one needs its own variable scope in order to reference a unique index.
for (var i = 0; i <= 10; i += 1) {
var $page_button = $('<a>', {
html : i,
click : makeHandler(i) // invoke makeHandler, which returns a function
});
$page_button.appendTo($wrapper);
}
function makeHandler(index) {
return function () {
console.log(index);
return false;
};
}
Here I made a makeHandler function that accepts the index argument, and returns a function that is used as the handler.
Because a function invocation sets up a new variable scope, and because a function is created and returned inside the makeHandler, each handler returned will reference its own scoped index number.

Example of closure in "Javascript: The good parts"

I am reading about closures in "Javascript: The Good Parts" book.
There is following example of using closures:
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (i) {
return function (e) {
alert(i + ":" + e);
};
}(i);
}
};
Is it correct example? Or much correct example would be following?
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (idx) {
return function (e) {
alert(idx + ":" + e);
};
}(i);
}
};
Variable i in the outer function and variable i in the inner function "nodes[i].onclick = function (i)" - it is two different variables.
And third function accesses variable from second function, not from the outermost.
Am I correct?
The two examples are identical. The whole point of a closure is to make an outer-scoped variable (i) into an inner-scoped variable (i/idx/foo, take your pick). The closure creates a "copy" of the variable, so that when the callback gets made, it has the correct value.
// outer-scoped i changes on each iteration
var i;
for (i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = function (i) {
// here i now refers to a different variable; while the outer i keeps iterating,
//this i is preserved at its current value.
return function (e) {
alert(i + ":" + e);
};
}(i);
}
Yes, the example correct. The i variable that you see as function parameter takes precedence over the outer i variable because it is declared in the local scope.
From javascript point of view - these two are identical. Therefore it's the question of taste. If you would have more closures inside, then it would be a good idea to use different names. But here the example is so simple - there is no need for that.
No, the two examples you posted is exactly the same. The only difference is just the parameter name for an function, but that doesn't matter, you could use whatever.

Scope troubles in Javascript when passing an anonymous function to a named function with a local variable

Sorry about the title - I couldn't figure out a way to phrase it.
Here's the scenario:
I have a function that builds a element:
buildSelect(id,cbFunc,...)
Inside buildSelect it does this:
select.attachEvent('onchange',cbFunc);
I also have an array that goes:
var xs = ['x1','x2','x3'...];
Given all of these, I have some code that does this:
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,function(){ CallBack(xs[i],...) },...);
}
The issue is that when onchange gets fired on one of those selects it correctly goes to CallBack() but the first parameter is incorrect. For example if I change the third select I expect CallBack() to be called with xs[2] instead I get some varying things like xs[3] or something else.
If I modify it slightly to this:
for(var i = 0; i < xs.length; i++)
{
var xm = xs[i];
buildSelect(blah,function(){ CallBack(xm,...) },...);
}
I'm still getting incorrect values in CallBack(). Something tells me this is scope/closure related but I can't seem to figure out what.
I simply want the first select to call CallBack for onchange with the first parameter as xs[0], the second select with xs[1] and so on. What could I be doing wrong here?
I should clarify that xs is a global variable.
Thanks
You need to capture that xm value by closing around it in its own scope.
To do this requires a separate function call:
buildCallback( curr_xm ) {
// this function will refer to the `xm` member passed in
return function(){ CallBack(curr_xm,...) },...);
}
for(var i = 0; i < xs.length; i++)
{
var xm = xs[ i ];
buildSelect(blah,buildCallback( xm ),...);
}
Now the xm that the callback refers to is the one that you passed to buildCallback.
If you have other uses for i that need to be retained, you could send that instead:
buildCallback( curr_i ) {
// this function will refer to the `i` value passed in
return function(){ CallBack( xs[ curr_i ],...) },...);
}
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,buildCallback( i ),...);
}
The problem is indeed scope-related -- JavaScript has only function scope, not block scope or loop scope. There is only a single instance of the variables i and xm, and the value of these variables changes as the loop progresses. When the loop is done, you're left with only the last value that they held. Your anonymous functions capture the variables themselves, not their values.
To capture the actual value of a variable, you need another function where you can capture the local variable:
function makeCallback(value) {
return function() { CallBack(value, ...) };
}
Each call to makeCallback gets a new instance of the value variable and if you capture this variable, you essentially capture the value:
for(var i = 0; i < xs.length; i++)
{
buildSelect(blah,makeCallback(xs[i]),...);
}
Yes, I think a closure would help:
for(var i = 0, l = xs.length; i < l; i++)
{
buildSelect(
blah,
function(xm){
return function(){
CallBack(xm,...)
};
}(xs[i]),
...
);
}
Edit: I also optimised your for loop slightly.
Edit: I guess I'll add an explanation. What you're doing is creating an anonymous function which takes one argument (xm) and calling the function straight away (with the parenthesis right after). This anonymous function must also return your original function as an argument of buildSelect().
Apparently there is a new let keyword that does what you want:
for(var i = 0; i < xs.length; i++)
{
let xm = xs[i];
buildSelect(blah,function(){ CallBack(xm,...) },...);
}

Categories

Resources