views:

83

answers:

2

The example below, is just an example, I know that I don't need an object to show an alert box when user clicks on div blocks, but it's just a simple example to explain a situation that frequently happens when writing JS code.

In the example below I use a globally visible array of objects to keep a reference to each new created HelloObject, in this way events called when clicking on a div block can use the reference in the arry to call the HelloObject's public function hello().

1st have a look at the code:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&gt;
<html><head><meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
   <title>Test </title>   
   <script type="text/javascript">      
      /*****************************************************
      Just a cross browser append event function, don't need to understand this one to answer my question
      *****************************************************/
      function AppendEvent(html_element, event_name, event_function) {if(html_element) {if(html_element.attachEvent) html_element.attachEvent("on" + event_name, event_function); else if(html_element.addEventListener) html_element.addEventListener(event_name, event_function, false); }}

      /******************************************************
      Just a test object
      ******************************************************/      
      var helloobjs = [];
      var HelloObject = function HelloObject(div_container)
      {         
         //Adding this object in helloobjs array
         var id = helloobjs.length; helloobjs[id] = this;

         //Appending click event to show the hello window 
         AppendEvent(div_container, 'click', function()  
         {              
            helloobjs[id].hello(); //THIS WORKS! 
         }); 

         /***************************************************/
         this.hello = function() { alert('hello'); }
      }      
   </script>
</head><body>
   <div id="one">click me</div>
   <div id="two">click me</div>      
   <script type="text/javascript">
      var t = new HelloObject(document.getElementById('one'));
      var t = new HelloObject(document.getElementById('two'));
   </script>
</body></html>

In order to achive the same result I could simply replace the code

     //Appending click event to show the hello window
     AppendEvent(div_container, 'click', function() 
     {             
        helloobjs[id].hello(); //THIS WORKS!
     });

with this code:

     //Appending click event to show the hello window
     var me = this;
     AppendEvent(div_container, 'click', function() 
     {             
        me.hello(); //THIS WORKS TOO AND THE GLOBAL helloobjs ARRAY BECOMES SUPEFLOUS!
     });

thus would make the helloobjs array superflous.

My question is: does this 2nd option in your opinion create memoy leaks on IE or strange cicular references that might lead to browsers going slow or to break???

I don't know how to explain, but coming from a background as a C/C++ coder, doing in this 2nd way sounds like a some sort of circular reference that might break memory at some point.

I also read on internet about the IE closures memory leak issue http://jibbering.com/faq/faq_notes/closures.html (I don't know if it was fixed in IE7 and if yes, I hope it does not come out again in IE8).

Thanks

A: 

The answer is in the body of the question. It seems weird to you because of your C++ background. The 2nd option is the Javascript-ish way of doing it.

Itay
+2  A: 

Aside:

var HelloObject = function HelloObject(div_container)

In general try not to use named inline function expressions. It doesn't usually get you anything and there are serious problems with them in JScript (IE). Either use a function HelloObject() { ... } statement, or a var HelloObject= function() { ... }; anonymous expression.

does this 2nd option in your opinion create memoy leaks on IE

‘Create’? No, your existing code already had leaks on IE.

The listener you applied to the click event has a closure, keeping a reference to the parent function scope. In both examples, the div_container object is in that scope, so you've got a circular reference:

div -> onclick ->
listener function -> parent scope ->
parent function -> reference to div

A reference loop containing a mixture of native JS objects and host objects (such as the div, a DOM Node) is what causes the memory leaks in IE6-7.

To stop this happening, you can pull the click function definition out of the parent:

function HelloObject(div_container) {
    AppendEvent(div_container, 'click', HelloObject_hello);
}
function HelloObject_hello() {
    alert('hello');
}

Now there is no closure, the div_container is not remembered, and there is no loop/leak. However, the hello function is just a function, with no idea to which div_container it belongs (other than by looking at the event/this it gets on click).

More typically you do need to remember the div, or, if you're doing things in an objecty way, the this:

function HelloObject(element) {
    this.element= element;
    AppendEvent(element, 'click', this.hello.bind(this));
}
HelloObject.prototype.hello= function() {
    alert('Hello, you clicked on a '+this.element);
};

(About function.bind.)

Which of course brings back the reference loop:

element -> onclick
bound hello function -> bound this
Helloer instance -> 'element' member
reference to element

Do you really care about this kind of refloop? Maybe not. It only really affects IE6-7; it's bad in IE6 as the memory isn't given back until you quit the browser, but then there's a growing school of thought that says anyone still using IE6 deserves everything they get.

On IE7, the memory leaks pile up until you leave the page, so it only matters for very long-running pages where you're throwing away old HelloObjects and binding new ones repeatedly. (In that case, a linear Array of HelloObjects that doesn't discard old values would also be a memory leak until page unload, in itself.)

If you do care, because you're working for some dingy corporate that runs IE6 and no-one ever closes their browsers, then (a) my condolences, and (b) yes, you will indeed have to implement something like the lookup object you had, to act as a decoupling layer between the DOM Nodes and the Function object you're using as an event listener.

Some frameworks have their own decoupling used for events. For example, jQuery attaches the event handler functions to a data lookup, indexed by an id that it drops into each Element object; this gives it decoupling for free, though it does have problems of its own...

If you're using plain JavaScript, here's some example helper code.

// Event binding with IE compatibility, and decoupling layer to prevent IE6-7
// memory leaks
//
// Assumes flag IE67 pre-set through eg. conditional comments
//
function EventTarget_addEventListener(target, event, listener) {
    if ('addEventListener' in target) {
        target.addEventListener(event, listener, false);
    } else if ('attachEvent' in target) {
        if (IE67)
            listener= listener.decouple();
        target.attachEvent('on'+event, listener);
    }
}

Function.decouple_bucket= {};
Function.decouple_factory= function() {
    function decoupled() {
        return Function.decouple_bucket[decoupled.decouple_id].apply(this, arguments);
    }
    return decoupled;
};
Function.prototype.decouple_id= null;

Function.prototype.decouple= function() {
    var decoupled= Function.decouple_factory();
    do {
        var id= Math.floor(Math.random()*(Math.pow(2, 40)));
    } while (id in Function.decouple_bucket);
    decoupled.decouple_id= id;
    Function.decouple_bucket[id]= this;
    return decoupled;
};
Function.prototype.release= function() {
    delete _decouple_bucket[this.decouple_id];
};
if (IE67) {
    EventTarget_addEventListener(window, 'unload', function() {
        Function.decouple_bucket.length= 0;
    });
}

// Add ECMA262-5 method binding if not supported natively
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}

With all that tedious plumbing out of the way, you can then just say:

function HelloObject(element) {
    this.element= element;
    EventTarget_addEventListener(element, 'click', this.hello.bind(this));
}
HelloObject.prototype.hello= function() {
    alert('Hello, you clicked on a '+this.element);
};

that might lead to browsers going slow or to break???

No, it's only really memory leaks (and then mostly in IE) that we're worried about when talking about refloops.

bobince
+1 and thanks for the detailed and extremely helpful explanation.
Marco Demajo