The code is rather long yet simple:
- 100 leaky JavaScript objects are created.
- 10 leaky elements are created from the JS objects.
- 1 element is removed and 1 is added 10000 times.
I assume that the detachEvent call is not functioning properly. Also, if you change this.eventParams from an array to a simple variable, the leak goes away. Why?
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Memory Leak With Fix</title> <style type="text/css"> .leakyEle { border: solid 1px red; background-color: Gray; } </style> <script type="text/javascript"> /******************************* MAIN ********************************/ var leakObjArray = new Array(); AddEvent(window, 'load', Startup, false); function Startup() { for(var i=0; i<100; i++) { leakObjArray.push(new LeakyObj(i)); } for(var j=0; j<10; j++) { leakObjArray[j].CreateLeakyEle(); } var container = document.getElementById('Container'); AddEvent(container, 'click', Run, false); alert('Close this dialog and click the document to continue.'); } function Run() { var k = 0; var l = 10; for(var m = 0; m<10000; m++) { leakObjArray[k].DestroyLeakyEle(); leakObjArray[l].CreateLeakyEle(); if(k<leakObjArray.length - 1) { k++; } else { k = 0; } if(l<leakObjArray.length - 1) { l++; } else { l = 0; } } for(var i=0; i<leakObjArray.length; i++) { leakObjArray[i].DestroyLeakyEle(); } alert('Test Complete.'); } /******************************* END MAIN ********************************/ /******************************* LEAKY OBJECT ********************************/ function LeakyObj(id) { this.id = id; this.leakyEle = null; this.containerEle = document.getElementById('Container'); this.clicked = false; this.eventParams = new Array(); } LeakyObj.prototype.CreateLeakyEle = function() { var leakyEle = document.createElement('div'); leakyEle.id = 'leakyEle' + this.id; leakyEle.className = 'leakyEle'; leakyEle.innerHTML = this.id + ' --- XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + '<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + '<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + '<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + '<br/>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; this.leakyEle = leakyEle; var _self = this; this.eventParams.push(AddEventWithReturnParams(this.leakyEle, 'click', function() { _self.EventHandler(); }, false)); this.containerEle.appendChild(leakyEle); } LeakyObj.prototype.DestroyLeakyEle = function() { if(this.leakyEle != null) { this.containerEle.removeChild(this.leakyEle); for(var i=0; i<this.eventParams.length; i++) { RemoveEventOverload(this.eventParams[i]); } this.leakyEle = null; } } LeakyObj.prototype.EventHandler = function() { this.leakyEle.style.display = 'none'; this.clicked = true; } /******************************* END LEAKY OBJECT ********************************/ /******************************* GENERAL FUNCS ********************************/ function AddEvent(elm, evType, fn, useCapture){ var success = false; if(elm.addEventListener) { if(evType == 'mousewheel') evType = 'DOMMouseScroll'; elm.addEventListener(evType, fn, useCapture); success = true; } else if(elm.attachEvent) { if(evType == 'mousewheel') { window.onmousewheel = document.onmousewheel = fn; success = true; } else { var r = elm.attachEvent('on' + evType, fn); success = r; } } else { success = false; } elm = null; return success; } function AddEventWithReturnParams(elm, evType, fn, useCapture) { var eventParams = new EventParams(elm, evType, fn, useCapture); AddEvent(elm, evType, fn, useCapture); return eventParams; } function RemoveEvent(elm, evType, fn, useCapture) { if(elm) { if(elm.removeEventListener) { elm.removeEventListener(evType, fn, useCapture); return true; } else if(elm.detachEvent) { var r = elm.detachEvent('on' + evType, fn); return r; } else { debugger; } } } function RemoveEventOverload(eventParams) { if(eventParams) { return RemoveEvent(eventParams.element, eventParams.eventType, eventParams.handler, eventParams.capture); } } function EventParams(elm, evType, fn, useCapture) { return { element: elm, eventType: evType, handler: fn, capture: useCapture } } /******************************* END GENERAL FUNCS ********************************/ </script> </head> <body> <div id="Container"></div> </body> </html>
looks like you’re pushing stuff onto the eventParams array inside CreateLeakyEle, but never removing it? Is that right?