Hello and welcome to our community! Is this your first visit?
Register
Enjoy an ad free experience by logging in. Not a member yet? Register.
Page 1 of 2 12 LastLast
Results 1 to 15 of 24
  1. #1
    Senior Coder
    Join Date
    Jun 2002
    Location
    near Oswestry
    Posts
    4,508
    Thanks
    0
    Thanked 0 Times in 0 Posts

    IE DOM memory leak strikes again ...

    I've been using my encapsulated anonymous functions technique more and more, and I think it exposes another memory leak in IE6 .. but cleaning it up is not so simple..

    Here's how it goes - this construct leaks a small amount of memory:
    Code:
    window.onload = function()
    {
    	var body = document.body;
    
    	document.body.onmousemove = function()
    	{
    		document.title = Math.random();
    	};
    };
    We can clean this (and all similar examples) up by iterating through document.all and setting the relevant expando property to null; in this case it's a mousemove:
    Code:
    window.attachEvent('onunload', function()
    {
    	var dlen = document.all.length;
    	for(var i=0; i<dlen; i++)
    	{
    		document.all[i]['onmousemove'] = null;
    	}
    });
    Okay, so far so good. But if the original handler is written as an encapsulated anonymous function instead of an expando property, like this:
    Code:
    body.attachEvent('onmousemove', function()
    {
    	document.title = Math.random();
    });
    then it's not exposed through document.all, and therefore isn't cleaned in the unload method. I discovered eventually that detachEvent works, except of course that it requires a reference to the function, which means this:
    Code:
    body.attachEvent('onmousemove', attachment = function()
    {
    	document.title = Math.random();
    });
    And then this:
    Code:
    body.detachEvent('onmousemove', attachment);
    And that works, but it's impractical. What I really want is another way to find these handlers and their function references, without having to save them in advance.

    So is there any object or collection this information might reside in? Since it's only for IE6, any ridiculous proprietary arcana would be an acceptible solution.
    Last edited by brothercake; 04-02-2005 at 12:26 AM.
    "Why bother with accessibility? ... Because deep down you know that the web is attractive to people who aren't exactly like you." - Joe Clark

  • #2
    Master Coder
    Join Date
    Feb 2003
    Location
    UmeŚ, Sweden
    Posts
    5,575
    Thanks
    0
    Thanked 83 Times in 74 Posts
    The best solution is AFAIK to make sure you never encapsulate a DOM reference in a closure.



    1. In iew one solution is to try to always use functions declared in the global scope as event handlers.

    2. Another trick is to set all variables that did contain DOM references to null before the outer function has closed.

    3. A third trick is to store IDs instead of DOM references where possible and to dynamically look them up when needed; or using an array of DOM references in the global scope which you store the index for locally to the same effect.

    4. A fourth trick is separation of scopes by using separate closures for all DOM references and event handlers - in other words using inner functions for everything except the event handler itself.



    As long as you can manage the single thing: No object or closure that is a property or event handler of a DOM object may contain DOM references, with the exception of when that reference is in the global scope.
    liorean <[lio@wg]>
    Articles: RegEx evolt wsabstract , Named Arguments
    Useful Threads: JavaScript Docs & Refs, FAQ - HTML & CSS Docs, FAQ - XML Doc & Refs
    Moz: JavaScript DOM Interfaces MSDN: JScript DHTML KDE: KJS KHTML Opera: Standards

  • #3
    Kor
    Kor is offline
    Red Devil Mod Kor's Avatar
    Join Date
    Apr 2003
    Location
    Bucharest, ROMANIA
    Posts
    8,478
    Thanks
    58
    Thanked 379 Times in 375 Posts
    See also this article to go deep into the problem and find some interesting solutions
    KOR
    Offshore programming
    -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

  • #4
    Senior Coder
    Join Date
    Jun 2002
    Location
    near Oswestry
    Posts
    4,508
    Thanks
    0
    Thanked 0 Times in 0 Posts
    But all of these solutions ultimately boil down to reducing the use of closures, and I'm not prepared to pollute the global scope with more functions, I want my entire script inside a single object.

    Is there no way I can clean this data without changing my coding style? What about your idea No. 2 - would that do it - if I nullified the whole object, would that clean up their inner closures?
    "Why bother with accessibility? ... Because deep down you know that the web is attractive to people who aren't exactly like you." - Joe Clark

  • #5
    Kor
    Kor is offline
    Red Devil Mod Kor's Avatar
    Join Date
    Apr 2003
    Location
    Bucharest, ROMANIA
    Posts
    8,478
    Thanks
    58
    Thanked 379 Times in 375 Posts
    boil down to reducing the use of closures,
    Not exactly... To my surprise, Mishoo (if you saw his article ) found a tricky solution by using a "nested/hidden" closure whithin another closure.... This way the handler have no direct acces to tha variable, so that the closure is not an active closure. It looks quite clever to me...
    KOR
    Offshore programming
    -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

  • #6
    Master Coder
    Join Date
    Feb 2003
    Location
    UmeŚ, Sweden
    Posts
    5,575
    Thanks
    0
    Thanked 83 Times in 74 Posts
    Quote Originally Posted by brothercake
    But all of these solutions ultimately boil down to reducing the use of closures, and I'm not prepared to pollute the global scope with more functions, I want my entire script inside a single object.
    Well, either you do 1 or 3 and remove closures, or you do point 4 and add closures. Or you do 2 and simply remove references. A smart compiler would also have shadowing variables as an alternative for 2, but I wouldn't expect JScript 5 to be that smart.

    Remember that closures mean shared scopes in which identifiers are looked up in, not shared values - you don't need to do anything to the actual values, only the identifier binding. This is what all these tricks try to attack.

    Another possibility that I just thought of that might work, is to use a non-variable to store the data - the arguments vector could be usable for that.

    Is there no way I can clean this data without changing my coding style? What about your idea No. 2 - would that do it - if I nullified the whole object, would that clean up their inner closures?
    Set the variables that contain references to any kind of JScript native and the closure no longer references a non-GC object. The trick is, as I said, to use whichever way you feel comfortable with to make sure you never close over a scope that contains a DOM reference. Clearing all DOM references out of the scope is one way to solve the problem. Making sure all DOM references are out of scope is what the three other tricks try to do.
    liorean <[lio@wg]>
    Articles: RegEx evolt wsabstract , Named Arguments
    Useful Threads: JavaScript Docs & Refs, FAQ - HTML & CSS Docs, FAQ - XML Doc & Refs
    Moz: JavaScript DOM Interfaces MSDN: JScript DHTML KDE: KJS KHTML Opera: Standards

  • #7
    Senior Coder
    Join Date
    Jun 2002
    Location
    near Oswestry
    Posts
    4,508
    Thanks
    0
    Thanked 0 Times in 0 Posts
    Yes I did see that, wrapping the closures in another anonymous function, but that only avoids the memory leak by making the method contents hidden to the containing object ... but then there isn't a reference to it, so how can it be re-used by the script? Or am I missing something?

    I have seen something by Mark Wubben that would do the job - http://novemberborn.net/javascript/event-cache - it's a sort of DOM reference controller that cleans up by having you passing all event constructors through it. It does essentially the same as my original construct, but designed for function references, and with this additional cleaning benefit.

    But ... I still can't see how that's any easier than assigning manual references myself as I build the functions - it wouldn't work as it is for anonymous functions because the cleaner requires a string reference to the function, and adapting it would be more complex than just making that one adaption to the approach I've already got ...

    What's the standard model? Is a handler added using addEventListener reference-able from any other interface beside the assignment that created it?
    Last edited by brothercake; 04-04-2005 at 05:21 PM.
    "Why bother with accessibility? ... Because deep down you know that the web is attractive to people who aren't exactly like you." - Joe Clark

  • #8
    Senior Coder
    Join Date
    Jun 2002
    Location
    near Oswestry
    Posts
    4,508
    Thanks
    0
    Thanked 0 Times in 0 Posts
    Quote Originally Posted by liorean
    Set the variables that contain references to any kind of JScript native and the closure no longer references a non-GC object. The trick is, as I said, to use whichever way you feel comfortable with to make sure you never close over a scope that contains a DOM reference. Clearing all DOM references out of the scope is one way to solve the problem. Making sure all DOM references are out of scope is what the three other tricks try to do.
    I don't understand that ... how can I ever avoid closing over a scope? Surely that would mean that method calls within OO constructors can't pass arguments? ie - aren't I limited to going
    Code:
    this.obj.onmouseover = this.mouseover;
    without ever being to pass arguments?
    "Why bother with accessibility? ... Because deep down you know that the web is attractive to people who aren't exactly like you." - Joe Clark

  • #9
    Master Coder
    Join Date
    Feb 2003
    Location
    UmeŚ, Sweden
    Posts
    5,575
    Thanks
    0
    Thanked 83 Times in 74 Posts
    Quote Originally Posted by brothercake
    I don't understand that ... how can I ever avoid closing over a scope?
    You can't, if you want to nest functions. The important part was the "that contains a DOM reference".

    IE users COM objects for it's internals, and those have one garbage collector. JScript uses native JScript objects and has it's own mark-and-sweep garbage collector. The problem is that the JScript GC and the COM GC don't share values, and will not communicate to get around circular references. Mozilla has four different types of garbage collectors, and quite a lot of code written by a GC unaware person for the application will be leaky, as well as some window/frame crossing code for documents. They solve this by manually communicating for application and by using the same GC for JavaScript and for DOM for document. I don't know how Lars and Jens solves this for op, but I know both konq/saf leaks even worse than iew.
    Surely that would mean that method calls within OO constructors can't pass arguments? ie - aren't I limited to going
    Code:
    this.obj.onmouseover = this.mouseover;
    without ever being to pass arguments?
    Not really, you just have to make sure that the arguments, if they are references, are either not formal parameters (and thus not stored in variables) or are cleared at some time.

    Something you can do is to create a cleansing function in each outer function, which will nullify all the variables in the scope. Add this function as listener on the document's beforeunload event and there you go, manual clearing of circular references when it should take place.

    Quote Originally Posted by brothercake
    Yes I did see that, wrapping the closures in another anonymous function, but that only avoids the memory leak by making the method contents hidden to the containing object ... but then there isn't a reference to it, so how can it be re-used by the script? Or am I missing something?
    If your purpose for creating the closure is just to have that reference, then it doesn't help one bit. But if you don't need the reference in the inner function it works perfectly fine.
    What's the standard model? Is a handler added using addEventListener reference-able from any other interface beside the assignment that created it?
    Nope, there's no such inspection facility. But it's still subject for the same possibly leaky garbage collection problem. And the fix is still the same.
    liorean <[lio@wg]>
    Articles: RegEx evolt wsabstract , Named Arguments
    Useful Threads: JavaScript Docs & Refs, FAQ - HTML & CSS Docs, FAQ - XML Doc & Refs
    Moz: JavaScript DOM Interfaces MSDN: JScript DHTML KDE: KJS KHTML Opera: Standards

  • #10
    Regular Coder
    Join Date
    Mar 2004
    Posts
    130
    Thanks
    0
    Thanked 0 Times in 0 Posts
    This is very interesting, though it does bother me. If I have a DOM object that is referenced by a JS object, is a memory leak caused? If so, then why is a memory leak not caused by creating a variable that points to an DOM object?

    If the above is true, e.g. if memory leak is created by storing a DOM object in a js object instance, then I must use document.getElementById in the instance's methods, which could be slow for mousemove operations or heavier mouseover/out operations.

    Does this simple class suffer any memory leaks? What if I were to use this.el = el; ? Would that create a memory leak?
    Should I instead store a reference to the element's ID in the object and use a callback in the handler? (as I have done below).
    PHP Code:
    // Button class -------------------------------------------------
    Button = function(el) {

        if(
    el.hideFocus)
            
    el.hideFocus true;
        
    this.id el.id;
        var 
    div el.getElementsByTagName("div")[0];
        
    this.divID = (div.id || (div.id el.id "Div"));
        
        
    addListener(el"click"Button.toggle );
        
    addListener(el"mouseover"Button.mouseover );
        
    addListener(el"mouseout"Button.mouseout );
    };

    Button.instances = { };

    Button.getInstance = function(el) {
        return 
    Button.instances[el.id] || (Button.instances[el.id] = new Button(el));
    };

    Button.prototype = {
        
        
    isDepressed false,
        
        
    isDisabled false,
        
        
    ondepress : function(e) { },
        
        
    onrelease : function(e) { },
        
        
    toggle : function(e) {
            
            var 
    el document.getElementById(this.id);
            if(
    this.isDisabled) return;
            
            if(
    el.blur
                
    el.blur();
                
            if(
    this.isDepressed) {
                
    removeClass(document.getElementById(this.divID), "button-active");
                
    this.isDepressed false;
                
    this.onrelease(e);
            }
            else {
                
    document.getElementById(this.divID).className += " button-active";
                
    this.isDepressed true;
                
    this.ondepress(e)
            }
        },
        
        
    mouseover : function(e) {
            if(!
    this.isDisabled && !this.isDepressed
                
    document.getElementById(this.divID).className += " button-hover";
        },
        
        
    mouseout : function(e) {
            if(!
    this.isDisabled && !this.isDepressed
                
    removeClass(document.getElementById(this.divID), "button-hover");
        },
        
        
    setEnabled : function(bEnable) {
            if(
    this.isDisabled == !bEnable) return;
            
            if(
    this.isDisabled
                
    removeClass(document.getElementById(this.id), "disabled");
            else 
                
    document.getElementById(this.id).className += " disabled";
            
            
    this.isDisabled = !bEnable;        
        }
        
    };

    Button.mouseover = function() { Button.getInstance(this).mouseover(); };
    Button.mouseout = function() { Button.getInstance(this).mouseout(); };
    Button.toggle = function() { Button.getInstance(this).toggle(); }; 
    Last edited by DHTML Kitchen; 04-13-2005 at 11:22 PM.

  • #11
    Regular Coder
    Join Date
    Mar 2004
    Posts
    130
    Thanks
    0
    Thanked 0 Times in 0 Posts
    I would much rather have used a closure, like this, right in the constructor:
    PHP Code:
    var ths this;
    addListener(el"click", function(e) { ths.toggle(e); } ); 
    That would solve scope issues and keep it clean and simple. I like using closures this way.

    Will it cause a mem leak? If so, how so?

    Thank you,

    Garrett

  • #12
    Regular Coder
    Join Date
    Mar 2004
    Posts
    130
    Thanks
    0
    Thanked 0 Times in 0 Posts
    ^^^

    anyone??

  • #13
    Senior Coder
    Join Date
    Jun 2002
    Location
    near Oswestry
    Posts
    4,508
    Thanks
    0
    Thanked 0 Times in 0 Posts
    My instinct is to say yes - that would cause a memory leak - but I must admit I don't fully understand this whole concept, and ... well ...

    There's come a point where the line has to be drawn - this far, no farther. All of this is a bug in IE, so I'll still add these cleaning functions to clear the exapando handlers they can, but I can't be bothered to care about it any more than that .. so IE has leaks .. so does Safari and Firefox (though over different things). Lots of APIs have leaks, and it's not the responsibility of individual application developers to jump through hoops trying to cater for them.

    JavaScript is an interpreted language - you shouldn't have to think about garbage collection in an interpreted language; period.


    Sorry .. bit grumpy I know. But I've so had enough of catering to IE's wilful non-compliance ... it just never ends ...
    Last edited by brothercake; 04-19-2005 at 07:48 PM.
    "Why bother with accessibility? ... Because deep down you know that the web is attractive to people who aren't exactly like you." - Joe Clark

  • #14
    Regular Coder
    Join Date
    Mar 2004
    Posts
    130
    Thanks
    0
    Thanked 0 Times in 0 Posts
    Hey man,

    yeah F. IE , I know. Personally I've been wishing MS'd stop making it since years ago.

    When you say expando handlers, do you mean assigned event handlers, as in
    span.onclick = handleClick; ?

    Have you done any testing?

    Is that causing any memory leak? Does it need to be explicitly cleared by span.onclick = null? We some test cases for that so we can see what to avoid -- and what we don't have to avoid -- so we can understand the situation clearer.

  • #15
    Kor
    Kor is offline
    Red Devil Mod Kor's Avatar
    Join Date
    Apr 2003
    Location
    Bucharest, ROMANIA
    Posts
    8,478
    Thanks
    58
    Thanked 379 Times in 375 Posts
    I guess that
    span.onclick = handleClick;
    will not cause a lack of memory, but
    span.onclick = function(){handleClick(this)}
    is a closure, as the object refereres to itself in a sort of self loop.
    KOR
    Offshore programming
    -*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*


  •  
    Page 1 of 2 12 LastLast

    Posting Permissions

    • You may not post new threads
    • You may not post replies
    • You may not post attachments
    • You may not edit your posts
    •