Do I need a closure inside a DOM event callback? - javascript

I'm trying to build a jQuery.live like function.
Helper is a class that has the _liveEvent and _addEventListener methods. Helper._addEventListener is just a CrossBrowser version of W3C addEventListener.
Helper.prototype._liveEvent = function(type, evt, ofunc) {
var elHand = document;
type = type.toUpperCase();
this._addEventListener(elHand, evt, function(me) {
// Inside here I use the `type` variable.
// I don't know why but it works.
for (var el = me.srcElement; el.nodeName !== 'HTML';
el = el.parentNode)
{
if (el.nodeName === type || el.parentNode === null) {
break;
}
}
if (el && el.nodeName === type) {
ofunc.call(el, me);
}
});
};
I'm running the Helper._liveEvent function twice with different types, and it works just fine. I thought that since the type variable was set inside the _liveEvent context the _addEventListener callback could see only the last version of that variable. But it's not the case, it seems to be working fine.
My questions are:
Why the _addEventListener callback can see both versions of the type?
Does it mean my code is leaking memory?
UPDATE
This other example made me understand this better, but I'm not sure I understand it fully yet.
function foo(i) {
setTimeout(function() {
console.log(i);
}, 400);
}
// Prints 1, 2, 3
for (var i = 1; i < 4; i++) {
foo(i);
}
function bar() {
for (var i = 1; i < 4; i++) {
setTimeout(function() {
console.log(i);
}, 400);
}
}
// Prints 4, 4, 4
bar();
​

It's because a separate closure scope is created for every instance of the anonymous function passed to _addEventListener(), each having its own values of elHand and type.
It depends on what you mean by "leaking". Every closure prevents objects that it contains from GC'ing. A closure is GC'ed when there are no more objects (say, anonymous functions like yours) referencing it. In this sense, yes, you have a memory leak, as you have no way to remove the added listener (anonymous function) thereby making the associated scope object eligible for GC.

Effectively, you already are creating a closure. This is why:
for( var i=0; i<10; i++) {
elem.onclick = (function(id) {alert(id);})(i);
}
works - calling the anonymous function creates a new closure with id set to the current value of i. (Personally I like to call the argument the same thing as the variable I want to use, so I can think of it as "locking" the value of the variable for that function).
As far as memory leaks go, two calls is not going to cause a leak. If GC works the way I think it does, it removes any closures that have no pointers to them. In particular, when you leave a page, any memory associated with that page is freed.

Related

Javascript Closures and self-executing anonymous functions

I was asked the below question during an interview, and I still couldn't get my head around it, so I'd like to seek your advice.
Here's the question:
var countFunctions = [];
for(var i = 0; i < 3; i++){
countFunctions[i] = function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
}
//The below are executed in turns:
countFunctions[0]();
countFunctions[1]();
countFunctions[2]();
When asked what would be the output of the above, I said count0,count1 and count2 respectively. Apparently the answer was wrong, and that the output should all be count3, because of the concept of closures (which I wasn't aware of then). So I went through this article and realized that I should be using closure to make this work, like:
var countFunctions = [];
function setInner(i) {
return function(){
document.getElementById('someId').innerHTML = 'count' + i;
};
}
for(var i = 0; i < 3; i++){
countFunctions[i] = setInner(i);
}
//Now the output is what was intended:
countFunctions[0]();//count0
countFunctions[1]();//count1
countFunctions[2]();//count2
Now that's all well and good, but I remember the interviewer using something simpler, using a self-executing function like this:
var countFunctions = [];
for(var i = 0; i < 3; i++) {
countFunctions[i] = (function(){
document.getElementById('someId').innerHTML = 'count' + i;
})(i);
}
The way I understand the above code, we are skipping the declaration of a separate function and simply calling and executing the function within the for loop.
But when I ran the below:
countFunctions[0];
countFunctions[1];
countFunctions[2];
It didn't work, with all the output being stuck at count2.
So I tried to do the below instead:
for(var i = 0; i < 3; i++) {
countFunctions[i] = function(){
document.getElementById('someId').innerHTML = 'count' + i;
};
}
, and then running countFunctions[0](), countFunctions[1]() and countFunctions[2](), but it didn't work. The output is now being stuck at count3.
Now I really don't get it. I was simply using the same line of code as setInner(). So I don't see why this doesn't work. As a matter of fact, I could have just stick to the setInner kind of code structure, which does work, and is more comprehensive. But then I'd really like to know how the interviewer did it, so as to understand this topic a little better.
The relevant articles to read here are JavaScript closure inside loops – simple practical example and http://benalman.com/news/2010/11/immediately-invoked-function-expression/ (though you seem to have understood IEFEs quite well - as you say, they're "skipping the declaration of a separate function and simply calling and executing the function").
What you didn't notice is that setInner does, when called, return the closure function:
function setInner(i) {
return function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
}
// then do
var countFunction = setInner("N"); // get the function
countFunction(); // call it to assign the innerHTML
So if you translate it into an IEFE, you still need to create (and return) the function that will actually get assigned to countFunctions[i]:
var countFunctions = [];
for(var i = 0; i < 3; i++) {
countFunctions[i] = (function(i){
return function() {
document.getElementById('someId').innerHTML = 'count' + i;
};
})(i);
}
Now, typeof countFunctions[0] will be "function", not "undefined" as in your code, and you can actually call them.
Take a look at these four functions:
var argument = 'G'; //global
function passArgument(argument){
alert(argument); //local
}
function noArguments(){
alert(argument); //global
}
function createClosure_1(argument){
return function (){
alert(argument); //local
};
}
function createClosure_2(argument){
var argument = argument; //local
return function (){
alert(argument); //local
};
}
passArgument('L'); //L
noArguments(); //G
createClosure_1('L') //L
createClosure_2('L') //L
alert(argument) //G
I think, first function is obvious.
In function noArguments you reference the global argument value;
The third and fourth functions do the same thing. They create a local argument variable that doesn't change inside them and return a function that references that local variable.
So, what was in the first and the last code snippet of your question is a creation of many functions like noArguments,
that reference global variable i.
In the second snippet your setInner works like createClosure_1. Within your loop you create three closures, three local variables inside them. And when you call functions inside countFunctions, they get the value of the local variable that was created inside the closure when they were created.
In the third one you assign the result of the execution of those functions to array elements, which is undefined because they don't return anything from that functions.

Javascript multiple dynamic addEventListener created in for loop - passing parameters not working

I want to use event listeners to prevent event bubbling on a div inside a div with onclick functions. This works, passing parameters how I intended:
<div onclick="doMouseClick(0, 'Dog', 'Cat');" id="button_id_0"></div>
<div onclick="doMouseClick(1, 'Dog', 'Cat');" id="button_id_1"></div>
<div onclick="doMouseClick(2, 'Dog', 'Cat');" id="button_id_2"></div>
<script>
function doMouseClick(peram1, peram2, peram3){
alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}
</script>
However, I tried to create multiple event listeners in a loop with this:
<div id="button_id_0"></div>
<div id="button_id_1"></div>
<div id="button_id_2"></div>
<script>
function doMouseClick(peram1, peram2, peram3){
alert("doMouseClick() called AND peram1 = "+peram1+" AND peram2 = "+peram2+" AND peram3 = "+peram3);
}
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
for (var i=0; i<names.length; i++){
document.getElementById(names[i]).addEventListener("click", function(){
doMouseClick(i, "Dog", "Cat");
},false);
}
</script>
It correctly assigns the click function to each div, but the first parameter for each, peram1, is 3. I was expecting 3 different event handlers all passing different values of i for peram1.
Why is this happening? Are the event handlers not all separate?
Problem is closures, since JS doesn't have block scope (only function scope) i is not what you think because the event function creates another scope so by the time you use i it's already the latest value from the for loop. You need to keep the value of i.
Using an IIFE:
for (var i=0; i<names.length; i++) {
(function(i) {
// use i here
}(i));
}
Using forEach:
names.forEach(function( v,i ) {
// i can be used anywhere in this scope
});
2022 edit
As someone is still reading and upvoting this answer 9 years later, here is the modern way of doing it:
for (const [i, name] of names.entries()) {
document.getElementById(name).addEventListener("click", () => doMouseClick(i, "Dog", "Cat"), false);
}
Using const or let to define the variables gives them block-level scope and the value of i passed to the handler function is different for each iteration of the loop, as intended.
The old ways will still work but are no longer needed.
2013 answer
As pointed out already the problem is to do with closures and variable scope. One way to make sure the right value gets passed is to write another function that returns the desired function, holding the variables within the right scope. jsfiddle
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
function getClickFunction(a, b, c) {
return function () {
doMouseClick(a, b, c)
}
}
for (var i = 0; i < names.length; i++) {
document.getElementById(names[i]).addEventListener("click", getClickFunction(i, "Dog", "Cat"), false);
}
And to illustrate one way you could do this with an object instead:
var names = ['button_id_0', 'button_id_1', 'button_id_2'];
function Button(id, number) {
var self = this;
this.number = number;
this.element = document.getElementById(id);
this.click = function() {
alert('My number is ' + self.number);
}
this.element.addEventListener('click', this.click, false);
}
for (var i = 0; i < names.length; i++) {
new Button(names[i], i);
}
or slightly differently:
function Button(id, number) {
var element = document.getElementById(id);
function click() {
alert('My number is ' + number);
}
element.addEventListener('click', click, false);
}
for (var i = 0; i < names.length; i++) {
new Button(names[i], i);
}
It's because of closures.
Check this out: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake
The sample code and your code is essentially the same, it's a common mistake for those don't know "closure".
To put it simple, when your create a handler function, it does not just accesses the variable i from the outer environment, but it also "remembers" i.
So when the handler is called, it will use the i but the variable i is now, after the for-loop, 2.
I've been struggling with this problem myself for a few hours and now I've just now managed to solve it. Here's my solution, using the function constructor:
function doMouseClickConstructor(peram1, peram2, peram3){
return new Function('alert("doMouseClick() called AND peram1 = ' + peram1 + ' AND peram2 = ' + peram2 + ' AND peram3 = ' + peram3 + ');');
}
for (var i=0; i<names.length; i++){
document.getElementById(names[i]).addEventListener("click", doMouseClickConstructor(i,"dog","cat"));
};
Note: I havn't actually tested this code. I have however tested this codepen which does all the important stuff, so if the code above doesn't work I've probably just made some spelling error. The concept should still work.
Happy coding!
Everything is global in javascript. It is calling the variable i which is set to 3 after your loop...if you set i to 1000 after the loop, then you would see each method call produce 1000 for i.
If you want to maintain state, then you should use objects. Have the object have a callback method that you assign to the click method.
You mentioned doing this for event bubbling...for stopping event bublling, you really do not need that, as it is built into the language. If you do want to prevent event bubbling, then you should use the stopPropagation() method of the event object passed to the callback.
function doStuff(event) {
//Do things
//stop bubbling
event.stopPropagation();
}

Why couldn't I pass the value in javascript?

for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click', function() { install(id); } );
}
}
No idea why id can't reach 'install' in function(). I am trying to bind every button (id from 50 to 100) with a click event to trigger the install(id) function. But it seems the variable id cannot reach install function. While I hard code it:
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click', function() { install( 56 ); });
}
}
it works! Please tell me why.
What you made is one of the most common mistakes when using Javascript closures.
By the way the very fact that this mistake is so common is IMO a proof that it's indeed a "bug" in the language itself.
Javascript supports read-write closures so when you capture a variable in a closure it's not the current value of the variable that is captured, but the variable itself.
This means that for example in
var arr = [];
for (var i=0; i<10; i++)
arr.push(function(){alert(i);});
each of the 10 functions in the array will contain a closure, but all of them will be referencing the same i variable used in the loop, not the value that this variable was having at the time the closure was created. So if you call any of them the output will be the same (for example 10 if you call them right after the loop).
Luckily enough the workaround is simple:
var arr = [];
for (var i=0; i<10; i++)
arr.push((function(i) {
return (function(){alert(i);});
})(i));
using this "wrapping" you are calling an anonymous function and inside that function the variable i is a different one from the loop and is actually a different variable for each invocation. Inside that function i is just a parameter and the closure returned is bound to that parameter.
In your case the solution is therefore:
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
$('#'+id).bind('click',
(function(id){
return (function() { install(id); });
})(id));
}
}
By not reaching the install(), I guess you mean you get all your install(id) behaves like install(100).
Reason why it doesn't work
This is caused by the javaSctipt closure. This line function() { install(id) } assign the id to the install() callback function. The id's value won't be resolved until install() is call when is far later after the loop is finished - the time when id has already reached 100.
The solution is create another closure the hold the current id value.
for (id = 50; id < 100; id++)
{
if($('#'+id).attr('class') == 'myField')
{
(function (id) {
$('#'+id).bind('click', function() { install(id); });
}) (id);
}
}
Here is a demonstration code:
var funcCollections = [];
for (id = 50; id < 100; id++)
{
if(true)
{
(function () {
var thatId = id;
funcCollections.push(function () {console.log(thatId,id)});
}) ();
}
}
// funcCollections[1]();
// 51 100
// undefined
// funcCollections[2]();
// 52 100
You can't pass a variable to the function you've bind. It loses the val. When you pass '56' it will be always 56, but when you pass a var, the JavaScript will not bind the value of the var in the loop.
When you loop over variables and you create anonymous functions(closure) that reference the loop variable they will reference the last value
also note that you don't limit scope the loop variable to the for loop(it's not declared with var) so that means that later modifications to that variable will be propagated to all closures.
take a look at this
It's down to variable scope.
The anonymous function you're binding to the click event of the $('#' + id) elements has no awareness of the id variable in the your sample code (assuming that your sample code is an excerpt from a function). Even if it did (e.g. you declared id outside of any function, giving it global scope), id would hold the value 100 when the click event was called, which isn't what you intend.
However, you could use $(this).attr('id') to get hold of the element's id value instead:
for (id = 50; id < 100; id++)
{
if($('#' + id).attr('class') == 'myField')
{
$('#' + id).bind('click', function()
{
install(parseInt($(this).attr('id')));
});
}
}
Check out the jQuery .bind() documentation, it shows how this can be used from within an event handler.

What are the pitfalls of adding properties to a function object within the function?

I found I could use this technique to retain a sort of "state" within an event handler, w/o having to involve outside variables...
I find this technique to be very clever in leveraging the fact that functions are actually objects in and of themselves, but am worried I'm doing something that could have negative implications of some sort...
Example...
var element = document.getElementById('button');
element.onclick = function funcName() {
// attaching properties to the internally named "funcName"
funcName.count = funcName.count || 0;
funcName.count++;
if (self.count === 3) {
// do something every third time
alert("Third time's the charm!");
//reset counter
funcName.count = 0;
}
};
Instead of doing that, you can use a closure:
element.onclick = (function() {
var count = 0;
return function(ev) {
count++;
if (count === 3) {
alert("3");
count = 0;
}
};
})();
That setup involves an anonymous function that the code immediately calls. That function has a local variable, "count", which will be preserved over the succession of event handler calls.
By the way, this:
var something = function dangerous() { ... };
is "dangerous" because some browsers (guess which, though Safari has had issues too) do weird things when you include a name on a function expression like that. Kangax wrote the issue up quite thoroughly.

Why can't I add an event to each element in a collection that refers to Itself rather than the last element in the "for(){}" statement

On Window's load, every DD element inside Quote_App should have an onCLick event appended that triggers the function Lorem, however, Lorem returns the nodeName and Id of the last element in the For statement rather than that of the element that trigged the function. I would want Lorem to return the nodeName and Id of the element that triggered the function.
function Lorem(Control){
/* this.Control=Control; */
this.Amet=function(){
return Control.nodeName+"\n"+Control.id;
};
};
function Event(Mode,Function,Event,Element,Capture_or_Bubble){
if(Mode.toLowerCase()!="remove"){
if(Element.addEventListener){
if(!Capture_or_Bubble){
Capture_or_Bubble=false;
}else{
if(Capture_or_Bubble.toLowerCase()!="true"){
Capture_or_Bubble=false;
}else{
Capture_or_Bubble=true;
};
};
Element.addEventListener(Event,Function,Capture_or_Bubble);
}else{
Element.attachEvent("on"+Event,Function);
};
};
};
function Controls(){
var Controls=document.getElementById("Quote_App").getElementsByTagName("dd");
for(var i=0;i<Controls.length;i++){
var Control=Controls[i];
Event("add",function(){
var lorem=new Lorem(Control);
lorem.Control=Control;
alert(lorem.Amet());
},"click",Controls[i]);
};
};
Event("add",Controls,"load",window);
Currently you click on any DD element Lorem always returns the nodeName and Id of the last DD element.
Lorem should return the nodeName and Id of the Control (Control[i]) that triggered Lorem.
How do I go about making this happen?
Thank you!
you need a closure inside the loop where you are attaching the event handlers to capture the value of i in each loop iteration.
for(var i=0;i<Controls.length;i++) {
(function() {
var Control=Controls[i];
Event("add",function(){
var lorem=new Lorem(Control);
lorem.Control=Control;
alert(lorem.Amet());
},"click",Controls[i]);
})();
};
Here I've created a closure above using JavaScript's good friend, the self-invoking anonymous function.
The reason a closure is required is that without it, the value of i at the point at which any event handler function is executed would be the value of i in the last loop iteration, not what we want. We want the value of i as it is in each loop iteration, at the point at which we declare each event handler function, thus we need to capture this value in each iteration. Using an anonymous function that executes as soon as it's declared is a good mechanism for capturing the desired value.
Another point, slightly off topic but it may help you out, is that event capturing is not supported in every browser (ahem, IE) but event bubbling is. This effectively makes the useCapture boolean flag in addEventListener quite useless for developing cross browser web applications. I'd therefore advise not to use it.
One more thing, JavaScript generally uses camel casing for function names and variable names. Pascal casing is generally used only for constructor functions (functions that create objects).
When you create a function that refers to variables outside of it, these references will be resolved at the time you call this function.
In your case:
var functions = [];
function outer() {
for (var i = 0; i < N; i++) { // <------------
functions[i] = function() { // |
alert(i); // <-- this 'i' refers to this one
}
} // At the end of the for loop, i == N (or N+1?)
}
functions[x](); // Will show N (or N+1)
// because that's the current value of i in the outer function
// (which is kept alive just because something refers to it)
What you want to do is capture the value of 'i' at each step of the loop, for later evaluation, e.g.:
var functions = [];
function outer() {
for (var i = 0; i < N; i++) { // <---------------------------------------
functions[i] = (function(my_i) { // <---- |
return function () { // | |
alert(my_i); // my_i refers to this which is a copy of 'i' there
}
}(i)); // run function(with my_i = the i at each step of the loop)
}
}
functions[x](); // Will show x
You can see there is an inner function that gets a copy of the current counter as a parameter. This inner function stays alive with that copy, so that later calls to the stored innest function returns the value of the my_i of the function that created it -- Clear? :-)
This is the wonderful world of closures. It may take a bit of mind-bending to first get it, but then you'll be glad you did, so go and google "javascript closures" to death!
This may be a more obvious variant of Russ Cam's answer:
function Controls() {
var Controls = document.getElementById("Quote_App").getElementsByTagName("dd");
var createHandlerFor = function(CurrentControl) {
return function() {
var lorem=new Lorem(CurrentControl);
lorem.Control=CurrentControl;
alert(lorem.Amet());
}
};
for (var i=0; i<Controls.length; i++) {
Event("add", createHandlerFor(Controls[i]), "click", Controls[i]);
}
};

Categories

Resources