Toon posts:

[JS] Soorten memory leaks en oplossingen?

Pagina: 1
Acties:
  • 195 views sinds 30-01-2008
  • Reageer

Verwijderd

Topicstarter
Ik ben nu een tijdje bezig met een vrij uitgebreide applicatie in JavaScript. Dit is eigenlijk de eerste keer dat ik iets met JavaScript maak en deze omvang heeft. Hierdoor kom ik (eigenlijk voor het eerst) tegen problemen aan met JavaScript en geheugen lekken. Met name eerdere reacties op bepaalde stukken code maakte me nieuwsgierig naar deze problemen.

Na een hoop dingen gevonden te hebben op Google en GoT merkte ik dat er nergens echt een goed overzicht van alle soorten lekken, en vooral (!) de oplossingen voor deze lekken.

In dit topic (uit 2003) wordt hetzelfde onderwerp behandeld, maar ik vind de reacties wat onduidelijk en onvolledig.

De hele openbaring (negatieve dan) heeft me enigszins in de war gebracht, waardoor ik nu niet meer durf om nieuwe code te typen omdat ik bang ben dat alles wat ik schrijf gaat lekken. ;)
Ik wil heel graag de goeie schrijfwijzen weten voor simpele dingen in JS en vraag me nu dus af welke soorten en oplossingen er zijn.

Ik ben ook voorbeelden tegengekomen van het 'oplossen' van geheugenlekken die bij een unload handmatig het geheugen van events vrij maken, maar zoals crisp in een eerdere post noemde is dit volgens mij eerder een workaround dan een oplossing. Tevens is de applicatie die ik nu maak op 1 pagina, dus een unload() komt niet zo snel voor...

Hieronder staan stukken code die ik heb geschreven naar aanleiding van wat ik heb gevonden. De meeste code is in de syntax hoe ik het op zou schrijven, andere stukken zijn in een syntax die ik heb gevonden op internet. (ik ben nog op onderzoek dus ga er niet vanuit dat alles correct is, naar mate er reacties komen zal ik deze post updaten)

VOORBEELDEN:

appendChild() volgorde (het voorkomen van het creëren van tijdelijke DOM nodes)
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
var div = document.getElementById('test');
var form = document.createElement('form');
var input = document.createElement('input');

// Goeie volgorde, voorkomt tijdelijke DOM tree
div.appendChild( form );
form.appendChild( input );

// Appenden op deze volgorde lekt geheugen
form.appendChild( input);
div.appendChild( form );
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * Vraag me af of het nu ook nodig is om alle attributen/properties van de DOM node 
 * pas te definiëren NAdat de node aan zijn parentElement gehangen is met .appendChild()
 */
// Dus:
var p = document.createElement('p');
var label = document.createElement('label');
var input = document.createElement('input');
form.appendChild( p );
p.appendChild( label );
p.appendChild( input );

label.setAttribute('username');
label.htmlFor = ''; // IE only
input.type = 'text';
input.name = 'username';
input.id = 'username';
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Of: (kan dit wel gewoon zonder te lekken???)
var p = document.createElement('p');
var label = document.createElement('label');
var input = document.createElement('input');

label.setAttribute('username');
label.htmlFor = ''; // IE only
input.type = 'text';
input.name = 'username';
input.id = 'username';

form.appendChild( p );
p.appendChild( label );
p.appendChild( input );


Events hangen aan DOM nodes
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
// Anonymous function (kan niet worden verwijderd door de garbage collector, 
// DUS lekt geheugen?)
var div = document.getElementById('test');
div.onclick = function(e) { alert('testje'); }

// Function reference in de event handler, lekt geen geheugen?
div.onclick = bla; }

function bla(e) {
    // this verwijst hier naar de <div>
    alert('testje');
}


JavaScript closures
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
function Tree(type) {
    this.type = type;
    this.branches = 10;
    return this;
}

Tree.prototype.grow = function() {
    var self = this; // Dit lekt geheugen, hoe kan ik dit voorkomen?

    var ul = document.createElement('ul');
    document.body.appendChild( ul );
    for (var i = 0; i < this.branches; i++) {
        var li = document.createElement('li');
        ul.appendChild( li );
        li.appendChild( document.createTextNode('branch ' + i) );
        li.onclick = function(e) {
            self.cutBranch( this );
        }
        li = null; // Removing DOM node from memory
    }
    ul = null; // Removing DOM node from memory
}

Tree.prototype.cutBranch = function( element ) {
    var parent = element.parentNode;
    if (parent) {
        parent.removeChild( element );
        return true;
    }
    return false;
}

var boompje = new Tree('den');
boompje.grow();

//
// Alternatief met function reference ipv anonymous function als event,
// en een argument meegeven

[...]

Tree.prototype._onItemClick(index) {
    // this verwijst hier naar het <li> element en NIET naar het Tree object ?
    return function() {
        alert( 'clicked item with index: ' + index );
    }
}

Tree.prototype.grow2 = function() {
    var ul = document.createElement('ul');
    document.body.appendChild( ul );
    for (var i = 0; i < this.branches; i++) {
        var li = document.createElement('li');
        ul.appendChild( li );
        li.appendChild( document.createTextNode('branch ' + i) );
        li.onclick = _onItemClick(i);
        li = null; // Removing DOM node from memory
    }
    ul = null; // Removing DOM node from memory
}


DOM object in JavaScript object zorgt voor lek
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 
function Grid(id)
{
    this.id = grid || 'grid';
    // volgende regel zorgt voor lek?
    this.table = document.getElementById(id) || document.createElement('table');
    this.table.id = id;

    // event function reference meegeven voorkomt lek?
    this.table.onclick = this.clickHandler;
}

Grid.prototype.clickHandler = function(e) {
    // this verwijst naar de <table>, niet erg handig
}

var gridje = new Grid('grid01');
Als iemand me zou kunnen vertellen welke voorbeelden goed zijn en welke slecht dan ben ik al een heel eind op weg. Thanks!

Gerelateerde URLs:

Verwijderd


  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

(jarig!)
appendChild() volgorde (het voorkomen van het creëren van tijdelijke DOM nodes)
Heb ik laatst nog zitten testen met Drip, maar lekt vziw geen geheugen (althans niet in IE6)
Events hangen aan DOM nodes
Anonymous functions kunnen doorgaans prima opgeruimt worden

Vorig jaar is hierover ook veel gediscusseerd op quirksmode:

http://www.quirksmode.org...5/10/how_do_i_create.html
http://www.quirksmode.org...5/10/more_on_creatin.html
http://www.quirksmode.org...5/10/creating_memory.html
http://www.quirksmode.org...5/10/memory_leaks_li.html
http://www.quirksmode.org...5/10/memory_leak_mys.html

Overigens, en voor de duidelijkheid: dit is voornamelijk een IE probleem vanwege hun suffe non-native DOM implementatie

[ Voor 10% gewijzigd door crisp op 13-03-2006 18:28 ]

Intentionally left blank


Verwijderd

Ik stoei al een tijd met zelfde problemen, op de een of andere manier lijkt het of de combinatie attachEvent / javascript objects altijd problemen oplevert. De vraag is even hoe koppel je events aan dom elementen met behoud van voorgaande events vanuit javascript objecten zonder memory te leaken.

Test de volgende test code maar eens met drip:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<html>
    <body>
         <form enctype="application/x-www-form-urlencoded" id="login-form" method="post" action="" onsubmit="return false;">
                <label for="username" id="userlbl"></label>
                <label for="password" id="passlbl"></label>
         </form>
    </body>
    <script>
        function leakageObject() {
        
            this.form = document.getElementById('login-form');
            this.username = document.getElementById('username');
            this.password = document.getElementById('password');
            var self = this;
            
            this.handleSubmit = function() {
                if (this.form) {
                    this.form.attachEvent('onsubmit',function() {
                        return false;
                    });
                }
            }
        }
        
        for(i=0;i<10;i++) {
            var leak = new leakageObject();
            leak.handleSubmit();
        }
    </script>
</html>

Verwijderd

Vreemd hij leakt op dit moment niet meer, ik zoek nog even naar een goed voorbeeld... heb leaks genoeg!

Verwijderd

kost wat moeite om een goeie test case te maken maar hier is ie dan:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* deze leakt */
<html>
    <body>
         <form enctype="application/x-www-form-urlencoded" id="login-form" method="post" action="" onsubmit="return false;">
                <label for="username" id="userlbl"></label>
                <label for="password" id="passlbl"></label>
         </form>
    </body>
    <script>
        function init() {
            var form = document.getElementById('login-form');
            if (form) {
                form.onsubmit = function(e) {
                    return false;
                }
            }       
        }
        
        window.attachEvent('onload', init, false);      
    </script>
</html>


JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* deze niet */
<html>
    <body>
         <form enctype="application/x-www-form-urlencoded" id="login-form" method="post" action="" onsubmit="return false;">
                <label for="username" id="userlbl"></label>
                <label for="password" id="passlbl"></label>
         </form>
    </body>
    <script>
        function submitform() {
            return false;
        }
        
        function init() {
            var form = document.getElementById('login-form');
            if (form) {
                form.onsubmit = submitform;
            }       
        }
        
        window.attachEvent('onload', init, false);      
    </script>
</html>


JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* deze leakt ook */
<html>
    <body>
         <form enctype="application/x-www-form-urlencoded" id="login-form" method="post" action="" onsubmit="return false;">
                <label for="username" id="userlbl"></label>
                <label for="password" id="passlbl"></label>
         </form>
    </body>
    <script>
        function init() {
            var form = document.getElementById('login-form');
            if (form) {
                form.attachEvent('onsubmit', function(e) {
                    return false;
                },false);
            }
        }
        
        window.attachEvent('onload', init, false);      
    </script>
</html>


JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* deze weer niet */
<html>
    <body>
         <form enctype="application/x-www-form-urlencoded" id="login-form" method="post" action="" onsubmit="return false;">
                <label for="username" id="userlbl"></label>
                <label for="password" id="passlbl"></label>
         </form>
    </body>
    <script>
        function submitform() {
            return false;
        }
        
        function init() {
            var form = document.getElementById('login-form');
            if (form) {
                form.attachEvent('onsubmit', submitform,false);
            }
        }
        
        window.attachEvent('onload', init, false);      
    </script>
</html>


En inderdaad, anonymous functions zijn de oorzaak. Self en this heb ik getest maar veroorzaken bij mij geen problemen.

  • Clay
  • Registratie: Oktober 1999
  • Laatst online: 18-02 12:13

Clay

cookie erbij?

Waar je/men om te beginnen al vanaf moet stappen is bv in treemenu's (of eigenlijk wat voor component dan ook) events op zoveel mogelijk afzonderlijke elementen plakken. Een heel treemenu heeft maar 1 onclick nodig, zeker niet 1 per li. De li waar je op klikte haal je wel uit de e.target || e.srcElement en als added bonus werkt het ook meteen op dynamisch toegevoegde nodes. Met een closure houd je de goeie scope erin, en onunload ruim je dat weer op :Y)

Met drag en drop kan je de mousemove en mouseup dynamisch setten als een drag actie begonnen wordt, en weer weghalen als de drag over is. Dat scheelt ook weer constant lopende mousemove listeners. etc. etc. UI's coden wordt gewoon steeds complexer, wat b.v. al erg veel gaat schelen is een goed boek lezen over designpatterns.

Instagram | Flickr | "Let my music become battle cries" - Frédéric Chopin


Verwijderd

Voor de meeste situaties heb ik inmiddels oplossingen gevonden voor de memory leak problemen.
Een situatie waarbij een closure gebruikt wordt om een objectreference + argument door te geven veroorzaakt altijd een leak en ik kan met geen mogelijkheid een manier vinden om hetzelfde te realiseren zonder closure.

Enig idee?

Voorbeeld code:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html>
    <body>
        <script>
            function greetingButton(greeting) {
                this.greeting = greeting;
                this.element = document.createElement('button');
                this.element.appendChild(document.createTextNode('Receive'));
                var self=this;
                this.element.onclick = function() {
                    greet(self,'test');
                }
            }
            
            greet = function(objref,greeting) {
                objref.greeting = greeting;
            };
            
            onload = function() {
                gb = new greetingButton('hello');
                document.body.appendChild(gb.element);
            };
        </script>
    </body>
</html>

  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

(jarig!)
Verwijderd schreef op dinsdag 14 maart 2006 @ 12:24:
Voor de meeste situaties heb ik inmiddels oplossingen gevonden voor de memory leak problemen.
Een situatie waarbij een closure gebruikt wordt om een objectreference + argument door te geven veroorzaakt altijd een leak en ik kan met geen mogelijkheid een manier vinden om hetzelfde te realiseren zonder closure.

Enig idee?
In dit geval kan je je opject-referentie opslaan in de DOM-space van je element hetgeen vziw geen geheugen lekt in IE:
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
function greetingButton(greeting) {
    this.greeting = greeting;
    this.element = document.createElement('button');
    this.element.appendChild(document.createTextNode('Receive'));
    this.element.objref = this;
    this.element.onclick = greet;
}

function greet () {
    this.objref.greeting = 'test';
}

Intentionally left blank


Verwijderd

Helaas, hij lekt :-( Ik heb inmiddels wel een werkende versie gezien in de dojo toolkit http://www.dojotoolkit.org

Ik zie het alleen niet echt zitten om een extra 71 kb aan script te gaan gebruiken.

Wellicht nog andere opties iemand?

  • crisp
  • Registratie: Februari 2000
  • Nu online

crisp

Devver

Pixelated

(jarig!)
Verwijderd schreef op dinsdag 14 maart 2006 @ 15:21:
Helaas, hij lekt :-( Ik heb inmiddels wel een werkende versie gezien in de dojo toolkit http://www.dojotoolkit.org

Ik zie het alleen niet echt zitten om een extra 71 kb aan script te gaan gebruiken.

Wellicht nog andere opties iemand?
De vraag is of een eventueel geheugen-lekje ook echt problemen oplevert, en dat is natuurlijk weer afhankelijk van je applicatie. Veel websites laten IE ook geheugen lekken, maar in de praktijk heb je daar meestal niet zoveel last van.
Daarbij kan je voor webapplicaties ook best wel eisen stellen aan de client; je zou gebruikers dus kunnen adviseren een andere browser dan IE te gebruiken ;)

Face it: je zit workarounds te verzinnen voor een probleem wat eigenlijk gewoon een bug in een (verouderde) browser is (een designfout zelfs).

[ Voor 17% gewijzigd door crisp op 14-03-2006 16:39 ]

Intentionally left blank

Pagina: 1