2

ok, this has been addressed by everybody, but yet I feel no closer to understanding what to do.

I want to have a loop that sets a bunch of click handlers and have each handler given unique parameters. I'm doing somehting like this now:

for (thisThing in things){
    myDiv=document.createElement('img');

    myDiv.onclick=function(){
        // do somehting!
        // but don't do anything with "thisThing" 
        // because it's got the wrong value in it by the time you call it!
        // but this function has to have the value of "thisThing" to work, dammit!
    }
}
  • All over the place the solution for this is said to be closures - wow great! except closures break ie (right?)
  • Elsewise, I can eval the code on the spot, baking in the varibles, but that seems ugly at best, and seems like it might break some thing

So what does this leave me? Are closures ok? is evaling ok? AM I THE ONLY PERSON DYNAMICALLY CREATING BUTTONS ON THE WEB?!? Any help will be much appreciated. sorry about the elementary &, in fact, frequently answered question.

this page has the most complete answer I've found thus far, however, it suggests that closures are the solution for all time. Curses! http://www.howtocreate.co.uk/referencedvariables.html

Trass Vasston
  • 679
  • 2
  • 6
  • 19

4 Answers4

9

Closures do not break IE.

(function(someThing) {
    myDiv.onclick=function(){
       // Access it as "someThing"
    }
})(thisThing);

jsFiddle.

The problem is the function assigned to the onclick property creates a closure where it has access to its parents variables.

By the time you are ready to click an element, its thisThing has been incremented to the final value that terminates the loop (the last property iterated over in your case) because it accesses the outer variable, not a copy.

By creating a new closure with a new self invoking function and passing thisThing, its value becomes the variable someThing and lives inside of the inner function only.

This is one of the ways of mitigating this known problem.

Note that in IE6 & 7 there is a memory leak. Consult RobG or Raynos's answer for a solution to this issue.

Community
  • 1
  • 1
alex
  • 479,566
  • 201
  • 878
  • 984
  • @Raynos I'm not sure. Any recommended reading on the subject? – alex May 19 '11 at 23:28
  • @alex http://www.ibm.com/developerworks/web/library/wa-memleak/ It's basically an issue with older versions of IE. If you make a link between a JS object and a DOM element a leak occurs. http://www.ibm.com/developerworks/web/library/wa-memleak/ – Raynos May 19 '11 at 23:30
  • @alex turns out it's a problem with `myDiv` having a reference to the function and the function having a reference to `myDiv`. You need a callback factory to avoid this leak >_ – Raynos May 19 '11 at 23:37
2

The problem is creating functions in loops is BAD. A little bit of refactoring makes your problem disappear.

function createDiv(thing) {
    var div = ...
    div.onclick = ...
}

for (thisThing in things) {
    createDiv(thisThing);
}

But this still leaks memory in older versions of IE.

It's because of this code :

var div = ...;
div.onclick = function() { ...;

The div has a refence to the function and the function has a reference to the div. This is a circular reference. To remove this problem you need a callback factory.

function createDiv(thing) {
    var div = ...
    div.onclick = createCallback(thing);
}

function createCallback(thing) {
    return function() {
         ...
    };
}

This means your onclick callback has NO reference to your div element.

alex
  • 479,566
  • 201
  • 878
  • 984
Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 1
    -1 for `creating functions in loops is BAD`. Closures are a very powerful feature of ECMAScript, though they can be misused, as can any language feature. Creating functions in loops is not of itself an issue. Creating closures where they aren't wanted is. – RobG May 19 '11 at 23:33
  • 1
    @RobG creating functions inside a loop is always a problem. There is nothing wrong with closures. but you should not create new functions inside a loop its a huge waste of memory. Closures are great. Closures are powerful, they are awesome. But that's no reason to write inefficient code. – Raynos May 19 '11 at 23:36
  • 2
    when you say creating a function inside the loop is bad, do you mean that in this example all of the DIVs being created in the loop can most likely use the same function (defined outside the loop) to handle `onclick` rather than having a unique handler created for each in the loop? (With some refactoring as needed.) If so, I certainly agree, but I don't think you spelled that out explicitly since your post also talks about callback factories - and on first read I thought you were recommending using the factory from within the loop, which of course would still be creating multiples. – nnnnnn May 20 '11 at 04:11
1

I want to have a loop that sets a bunch of click handlers and have each handler given unique parameters. I'm doing somehting like this now:

> for (thisThing in things){
>     myDiv=document.createElement('img');
> 
>     myDiv.onclick=function(){
>         // do somehting!
>         // but don't do anything with "thisThing" 
>         // because it's got the wrong value in it by the time you call it!
>         // but this function has to have the value of "thisThing" to work,
> dammit!
>     } }

All over the place the solution for this is said to be closures

No, you don't want closures at all. You are trying to not have closures.

except closures break ie (right?)

Wrong.

There was an issue with IE and circular references involving DOM elements causing memory leaks. That issue has largely been fixed, however it is much misunderstood. For example:

function addListener(element) {
    var someVar = ...;
    element.onclick = function() {
        // this function has a closure to someVar
    };
}

Causes a cicular reference involving the closure to someVar when addListener is called. There was some pretty simple fixes for that. What you want to do is not have a closure to someVar, but use the value. That can be done a number of ways, but the simplest is:

function addListener(element) { var someVar = ...; element.onclick = (function(differentVar) { alert(differentVar); })(someVAr); }

So now the value of someVar is passed to differentVar, which is a local variable of the inner function. Of course the inner function may still have a closure to someVar, but the use of the use of an immediately called anonymous function breaks the closure between differentVar and someVar.

As a one-of, there is no issue with the closure anyway. But where you are attaching a number of listeners and they all have a reference to someVar, then they all share the same value. Using an immediately called anonymous function in between breaks that chain.

Edit

Sorry, that last example is incorrect, my bad.

function addListener(element) {
    var someVar = ...;
    element.onclick = (function(differentVar) {
        return function() {
          alert(differentVar);
        };
    })(someVAr);
}
RobG
  • 142,382
  • 31
  • 172
  • 209
  • You can have my +1 in 16 minutes :) – alex May 19 '11 at 23:43
  • Shouldn't you be returning a function on the `onclick` event in the last example because it just alerts `differentVar` on dom ready? I'm trying to understand your example.. could you give me a hand? – Samson Nov 15 '12 at 09:59
  • Anyway it seems I can't make it work using: function `addListener(element) { var someVar = 1; element.onclick = (function(differentVar) { var f= function(){ alert(differentVar); }; return f; })(someVar);` – Samson Nov 15 '12 at 10:07
0

Closures don't break IE.

for (thisThing in things){
    if(!things.hasOwnProperty(thisThing)) continue;

    var myDiv = new document.createElement('img');
    myDiv.onclick = (function(thing){
        // Instance specific vars and other thigns
        return function() {
            // More stuff
        }
    })(thisThing);
}
Ryan Olds
  • 4,847
  • 28
  • 24