Preventing AJAX memory leaks - javascript

I am working on a web application that is designed to display a bunch of data that is updated periodically with AJAX. The general usage scenario would be that a user would leave it open all day and take a glance at it now and then.
I am encountering a problem where the browsers memory footprint is growing slowly over time. This is happening in both Firefox and IE 7 (Although not in Chrome). After a few hours, it can cause IE7 to have a footprint of ~200MB and FF3 to have a footprint of ~400MB.
After a lot of testing, I have found that the memory leak only occurs if the AJAX calls are being responded to. If the server doesn't respond to anything, I can leave the page open for hours and the footprint won't grow.
I am using prototype for my AJAX calls. So, I'm guessing there is an issue with the onSuccess callback creating these memory leaks.
Does anyone have any tips on preventing memory leaks with prototype / AJAX? Or any methods on how to troubleshoot this problem?
EDIT: found out the issue lies in a js graphing library I am using. Can be seen here.

The biggest thing you can watch out for is events, and how you assign them.
For instance, take this scenario (since you haven't provided one):
<div id="ajaxResponseTarget">
...
</div>
<script type="text/javascript">
$(someButton).observe('click', function() {
new Ajax.Updater($('ajaxResponseTarget'), someUrl, {
onSuccess: function() {
$$('#ajaxResponseTarget .someButtonClass').invoke('observe', 'click', function() {
...
});
}
});
});
</script>
This will create a memory leak, because when #ajaxResponseTarget is updated (internally, Prototype will use innerHTML) elements with click events will be removed from the document without their events being removed. The second time you click someButton, you will then have twice as many event handlers, and garbage collection can't remove the first set.
A way to avoid this is to use event delegation:
<div id="ajaxResponseTarget">
...
</div>
<script type="text/javascript">
$('ajaxResponseTarget').observe('click', function(e) {
if(e.element().match('.someButtonClass')) {
...
}
});
$(someButton).observe('click', function() {
new Ajax.Updater($('ajaxResponseTarget'), someUrl);
});
</script>
Because of the way DOM events work, the "click" on .someButtonClass will fire also on #ajaxResponseTarget, and Prototype makes it dead simple to determine what element was the target of the event. No events are assigned to elements within #ajaxResponseTarget, so there is no way for replacing its contents to orphan events from targets within.

I may be wrong but it sounds like you are creating closures around the response object. Each response object will be different which results in an increased memory footprint.

Related

How to remove an event out of content script scope?

I'm trying to unbind an event from a specific element and upon research, I found this. Which is useful. In itself. I didn't know you could do that. But:
Is there a way to make it work in a browser/Chrome extension? I'm talking about content scripts.
The reason why this doesn't work the way it's described there is that the website which has attached the event in question with its own script is using a different jQuery object than the one in my extension's includes/ folder. And I can try to search the event via jQuery._data(el, 'click'); but that is my jQuery object, not the one of the website where the events are apparently stored. I'm glad I figured that out after hours of fiddling around.
Or maybe it is possible to access the website's jQuery object itself?
EDIT:
What I'm ultimately trying to achieve works in theory but … it's complicated. The original script uses a plugin event and keeps reinstalling it with .on('mouseleave',….
Anyway, this is what I got thanks to you, pdoherty926:
var $el = $('div.slideshow');
$('h2', $el).click(function(){ console.log('ouch!'); }); // test event
var $slides = $('.slides', $el).detach();
$copy = $el.clone(false);
$slides.prependTo($copy);
$el.replaceWith($copy);
The test event doesn't get triggered but the event I'm actually trying to remove still fires. I can imagine figuring it out, though, now that I got closer to my goal.
Okay, the aforementioned re-installation on mouseleave really messed up this otherwise satisfying suggestion. (The site is using the jQuery Timer plug-in by Cyntaxtech). So here's how I solved it instead: I simply changed the class name (-.-' )
Now the re-installation code cannot find the element anymore.
This is how my finished script looks like:
function stop_happening() {
var $el = $('div.fullwall div.slideshow');
$el
// first, stop the current automation.
.timer('stop') // Timer plug-in
// next, change class name in order to prevent the timer
// from being started again.
.removeClass('slideshow').addClass('slideshow-disabled-automation');
//--- copied some extra code from the website itself for the onclick
// events which are supposed to keep working. I wish I could do *that*
// programmatically but I'm glad I got as far as I got. ---//
// […]
}

Iframes and memory management in Javascript

I have links that load pages into iframes. I have been monitoring the accumulation of data in memory using Google Chrome's memory heap profiler and I noticed some leaks in memory.
I loaded the page and took the first snapshot which added up to 2.69 MB. I clicked the link that opens a page into an iframe and took another snapshot giving me 14.58 MB in total. I removed the iframe using the following jquery snippet:
$('#myframe').unbind();
$('#myframe').remove();
/*
* By the way, I also tried $('#myframe > *') as a selector.
* It still didn't work. Even if it would, it doesn't look like a viable solution to me.
* It looks too resource intensive.
*
* I forgot to mention that I am not using Ajax to load my pages
*/
I took another snapshot and got 5.28 MB which indicated a deviation of 2.59 MB from the initial value, which according to my understanding indicates memory leackage.
Now, my question is: If I remove an iframe (which includes the document loaded in it) doesn't the garbage collector find it necessary to also remove all the objects contained in that document from memory? Or will I have to do this manually?
I thought that if I load a document into an iframe, it's size will not affect the memory use on the parent page. I though it will be considered a separate window, but obviously that wasn't a well informed assumption on my part.
Any suggestions on how to tackle this?
Thank you.
In the iframe, trigger a reload before removing it and then remove it.
Remove
<iframe src="url" />​
$('a').click(function(){
$('iframe')[0].contentWindow.location.reload();
setTimeout(function(){
$('iframe').remove();
}, 1000);
});​
DEMO here.
Addionally, you can do a manual cleaning up too - i.e. if you have data in your cookies or HTML5 localStorage.
window.onbeforeunload = function(){
$(document).unbind().die(); //remove listeners on document
$(document).find('*').unbind().die(); //remove listeners on all nodes
//clean up cookies
/remove items from localStorage
}
If any objects from the iframe is referenced in a object in the main window that object won't be removed from the DOM, so, if you have something like this:
Main window:
var object = {};
function iframe_call(data){
object.iframe_data = data.something
}
iframe:
function onClick(){
parent_object.iframe_call(this);
}
this happens especially if you refer DOM objects.
var frame = document.getElementById("myframe");
frame.src = "about:blank";
This worked from me and prevented memory leaks. Ig you must destroy the parent of the iframe, do it with some delay to prevent memory leak

refactoring javascript event handlers - is this a reasonable thing to do?

I have inherited a relatively big javascript app (~3k lines of code) and just do parttime javascript. I want to put all the jQuery event handlers into a more sane organization. I was thinking something like this:
before:
$('#click-here').click(function(){
//a bunch of dom manipulations and with 30 of them makes the app fairly convoluted
});
after:
function result_click(){
var global_id=$(this).data('global-id');
alert('here are the results of clicking: ' + global_id);
}
$(document).ready(function(){
$('#click-here').on('click',result_click);
});
</script>
<div id='click-here' data-global-id='23'>click HERE!</div>
This would be a fairly easy refactoring and would seem to structure it better. The event handling part seems to be the most out of control. Is this a reasonable way to do this? It would seem fairly easy to make this into a backbone.js app once it gets to this point.
thx
Yes, it's good to pre-define functions and then pass a reference to the functions into the click event.

What are the benefits and/or pitfalls of calling a function directly from an anchor tag vs creating events onload?

Is there a proper/standard way?
Link
vs
<script>
$(document).ready(function(){
$('#link1').click(function(){ ... });
});
</script>
Link
Personally I prefer the second. It allows me to separate my markup and script. This means that scripts could be placed in a separate file and since scripts are static resources they get cached, minified, obfuscated, ... And reduced markup size obviously leads to reduced bandwidth and so faster loading site. In a web page there are really 3 notions that should not be mixed:
markup
scripting
styling
From the top of my head:
The first one only allows one handler per time on a single element
The first one cannot have access to anything but global scope
The first one doesn't allow for event delegation, because some events need to use event capture in standards compliant browsers for delegation to work (namely blur and focus), and you cannot select whether to use capture or bubble there.
The first one ... read #DarinDimitrov's answer.
You would use the first one when... ask facebook or hotmail. I've got nothing on that.
My 2c:
Nowadays with the advent of heavy use of ajax and the versatility of jQuery, I like to centralize the processing management.
The
<a href='#' onClick= ...></a>
does not necessary require the href. On that, I, myself, avoid extra stress whenever not needed. So I do not code href on anchors that resolve to onClick. Secondly, unless I have a special need to manage anchors (like $('a ...)....), I do not use anchors either. I just use plain div or span or even paragraph (< p>).
the a (anchor) allows to automatic cursor:pointer and text-decoration handling for anchors.
The
$(document).ready( function () {
$('#link').click(...
is a good way to centralize processing where you can have all entries documented right there:
function a() { .. }
function b() { .. }
function c() { .. }
....
$(document).ready( function () {
// originated on thatfile.html
$('#clicka').click(a());
// originated on thisfile.asp
$('#clickb').click(b());
// originated on form c in file myforms.asp
$('#clickc').click(c());
});

Mootools problem when zoomed in

I am using Mootools extensively for a site which I am developing. But recently I noticed a problem that the animations slow down alot when I zoom (using the browsers Zoom In) in into the site. What could be a possible reason for this problem ? Or is this problem inherit in Mootools itself. This happens in Chrome 6.0.472 as well as Firefox 3.6.8.
Thanks,
Nitin
many things are wrong here with regards to speed optimisations.
lets take a look at this mouseover code that seems to slow down:
this.childNodes.item(1).style.left="0px";
this.getElements('div').setStyles({'opacity':'1'});
this.getElements('div').set('morph', {duration:'normal',transition: 'sine:out'});
this.getElements('span').set('morph', {duration:'normal',transition: 'sine:out'});
this.getElements('div').morph({'left':'-28px'});
this.getElements('span').morph({'left':'-30px','color':'#FFF'});
obviously this will work as it does but it's so very wrong i don't know where to begin.
the idea is to abstract and setup the repetitive tasks so they are done as a one off.
consider line by line the code above:
this.childNodes.item(1).style.left="0px";
this is wrong for a mootools app anyway, it would need to be this.getFirst().setStyle("left", 0);
the this.getFirst() is a lookup, it should be cached - although that's not a slow one.
then comes the bad part.
you select all child divs 3 times and all spans twice, where NO SELECTION should be applicable. VERY EXPENSIVE
you reset the Fx.morph options every mouseover event where there are no changes (although you seem to have a different duration for mouseenter and mouseleave - this is expensive)
consider this code:
[document.id("menu1"), document.id("menu2")].each(function(el) {
// use element storage to save lookups during events
el.store("first", el.getFirst());
el.store("divs", el.getElements("div"));
el.store("spans", el.getElements("span"));
// store the fx.morph options once and for all, no need to do so
// on every event unless you are changing something
el.retrieve("divs").set("morph", {
duration: 'normal',
transition: 'sine:out',
link: 'cancel'
});
el.retrieve("spans").set("morph", {
duration: 'normal',
transition: 'sine:out',
link: 'cancel'
});
// add the events
el.addEvents({
mouseenter: function(e) {
// retrieve the saved selectors from storage and do effects
this.retrieve("first").setStyle("left", 0);
this.retrieve("divs").morph({
"left": -28
});
this.retrieve("spans").morph({
'left': '-30px',
'color': '#FFF'
});
}
});
});
it will save a lot of processing on the events.
similarly, there are plenty of places where you are not really using the mootools api.
document.getElementById(curr).style.cursor="pointer";
$(this).removeEvents -> no need for $, this is not jquery.
document.getElementById("lightbox").style.visibility="hidden";
m=setTimeout('gallery()',5000); --> use the mootools var timer = (function() { ... }).delay(5000);, don't use strings with setTimeout/interval as it forces eval and reflows but pure anon functions
etc etc.
you really can make a day out of refactoring all this and making it 'nice' but it's going to be worth it.
also, learn about chaining
$("ribbon").set('morph', {duration:'long',transition: 'bounce:out'});
$("ribbon").morph({'top':'-10px'});
$("ribbon").addEvents({
this is calling up a selector 3 times. instead you can:
store it. var ribbon = $("ribbon"); ribbon.set...
chain it. $("ribbon").set("morph", {duration: 500}).morph({top:-10}).addEvents() - mootools element methods tend to return the original element so you can take the response of the last function and apply more to it.
1 is better for readibility, 2 is faster to do.
also. you have way too many global variables which makes your scope chain lookups more expensive, this will affect many call ups and places. try to namespace properly, if you need to access real global vars from functions and closures, use window.varname etc etc.
Another possible improvement here would be the use of event delegation (event bubbling will cause events to fire up the dom tree to the parents, mootools has an api to deal with it so you can add singular events to parent elements and not have to attach nnn events to all children) - look it up.
P.S. please don't take this in the wrong way - it's not meant to rubbish your work and it's just some constructive (i hope) advice that can help you bring it to the next level. good luck :)
I haven't seen any specific code in MooTools or any other library that checks if browser is zooming during animation, so I think that animation slows down, since browser using more CPU for computing zooming process.

Categories