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>