Loading and unloading js

I’m currently working on AJAXifying an existing site. A problem I’m having is that some pages require extra js to be loaded. Loading the js is easy - just use js to add a script tag to the page pointing at the js file to be loaded.

The issue comes when moving to a page that doesn’t need that previously loaded js - you can remove the script tag, but the js from that file will stay loaded in the browser.

A possible solution I thought would be to ensure that everything in the loaded js file is encapsulated, and when that file is removed, then remove any event handlers it has assigned. That way there will be no references left to anything in the previously loaded js file, and the gc should clean it up.

However, if I run a test in IE and debug using Developer Tools, if I add the js, remove it, then add it again, I see the added js file twice in the sources list (and more if I repeat the removing and adding process more times).

I’m not really au fait with js profiling to determine whether this is an actual issue or not.

Example page: http://www.iliveinabin.com/AJAX-test-del/

I guess my actual question would be, is what I’m doing sensible, or does anyone have a better suggestion?

Thanks

Dave

Main js included in the page that loads and removes the additional js file:

function loadScript(e)
{
    if (e.preventDefault) {
        e.preventDefault();
    }
    
    if (!document.getElementById('ajax')) {
        var script = document.createElement('script');
        script.src='./ajax.js';
        script.id = 'ajax';
        //if we're adding a new script we need to wait for it to finish loading before
        //triggering the DOMContentLoaded event
        script.onload = triggerDOMContentLoadedOnScriptLoad;
        document.body.appendChild(script);
    } else {
        despatchEvent('DOMContentLoaded', document);
    }
    
    function triggerDOMContentLoadedOnScriptLoad(e)
    {
        despatchEvent('DOMContentLoaded', document);
    }
    return false;
}

function removeScript(e)
{
    if (e.preventDefault) {
        e.preventDefault();
    }
    
    var script = document.getElementById('ajax');
    if (script) {
        despatchEvent('scriptRemoved', script);
        script.parentNode.removeChild(script);
    }
    return false;
}

JS file that is loaded / removed as needed (ajax.js):

(function()
{
    console.log('ajax.js loaded');
    var documentLoaded = (function()
    {
        var longstr='';
        return function(e)
        {
            for (var chars = 'abcdefghijklmnopqrstuvwxyz ', i = 0; i<1000000; i++) {
                longstr += chars.charAt(Math.floor(Math.random() * (27 - 1)) + 1);
            }
            alert(longstr.length);
        }
    })();
    document.addEventListener('DOMContentLoaded', documentLoaded, false);
    document.getElementById('ajax').addEventListener('scriptRemoved', cleanup, false);

    function cleanup(e)
    {
        document.removeEventListener('DOMContentLoaded', documentLoaded, false);
        document.getElementById('ajax') && document.getElementById('ajax').removeEventListener('scriptRemoved', cleanup, false);
        console.log('ajax.js removed');
    }
})();

Edit: After amending my code slightly and doing a bit of very rudimentary testing, it appears that the gc frees up the used memory in FF once the script and handlers have been removed (though it can take a minute or two before the gc runs). In IE it looks like the memory stays allocated to IE, but IE will re-use this memory.

Unless I miss my guess, you’re overthinking some of this. Web pages are stateless, so the DOM and js loaded only apply to the current page. Unless you’re completely striking the page and reloading it (in which case, using javascript and ajax is not the right approach as you’re going to cause more bandwidth and scripting issues in the long run), there’s no need to worry about killing any event handlers as you load the new content onto your page.

Hi Dave

But if you have an event handler assigned to an element that is not removed when the new ‘page’ is loaded, (in my example I’m assigning a handler to document), then this handler would persist into the next ‘page’ unless you explicitly remove it.

In a similar way, if you didn’t encapsulate everything in the added js file, you’d have at least one item in the global scope, and so the gc wouldn’t clean it up.

I’ve done a bit more testing now and it appears that both IE and Chrome don’t free up the memory after removing the added js file, but do then free the memory when you open the developer tools!

OK, take this as personal opinion, but if you’ve got an event handler assigned to an element that is going to remain on the page but isn’t needed on every “page”, then you’ve got the event handler on the wrong element and you should rethink where that event handler is attached.

Thanks, that’s a good point.

The problem I have is that I’m listening for the DOMContentLoaded event, which is fired from the document on a normal (non AJAX) page load. I manually fire the DOMContentLoaded event on the document when loading a new ‘page’ via AJAX. Since the script can’t tell whether it’s being loaded ‘normally’ or ‘ajaxly’, I’m not sure if there would be a way to get around attaching a handler to the document?

I guess I could set a global var when the page is being loaded via AJAX, then check for that in the added script. If it is set, add a listener for a custom event on the script element, if not then listen for DOMContentLoaded on the document. And in the function that fetches the pages via AJAX, send a custom event on the script element as well as triggering DOMContentLoaded on the document.

Why are you listening for that event?

If the JavaScript is at the bottom of the page where it belongs that event should already have triggered before the JavaScript finishes loading in the first place.

When using Ajax to load new content into the page the code that runs to actually insert that content into the DOM can add event listeners as required by that content.

If you use the same function name inside of the scripts you are loading to go with the content then the functions for the listeners that the current content doesn’t need can be empty so that even if the listener does get called then nothing will happen.

I don’t see how that can be possible?

What I’m doing at the moment is the main js fetches the new content, which includes any script elements it needs. It detaches the script elements, inserts the new content, removes any old script elements that are no longer needed, and inserts the new script elements if they don’t already exist. It then triggers DOMContent loaded so all scripts can do any init stuff they need to (like adding event listeners on the new content).

For the code that inserts the new content to add handlers to the new content, how would it be able to know what handlers need adding where?

Also, wouldn’t it mean that any event handlers in the new js file(s) would have to be in the global scope, thus breaking their encapsulation and causing a memory problem when the script is no longer needed? Otherwise how could the script that pulls in the content assign these handlers?

Then I don’t understand what you are doing. You appear to have picked a really convoluted way of doing something that should be quite simple with a few simple ajax calls to load new content into the existing page.

[quote=“felgall, post:8, topic:110766”]
You appear to have picked a really convoluted way of doing something that should be quite simple with a few simple ajax calls to load new content into the existing page.
[/quote] That’s probably true, but I can’t see a better way of doing it? How would you do it?

Just to update this, felgall was quite correct in that I was over thinking it, and also quite correct that you can just include the script at the bottom of the page without bothering with any event handlers to trigger the script’s init at all.

As I mentioned in my initial post, I was seeing an issue in IE where loading a script would cause the script to persist in memory until the page was closed. So visiting 10 pages via AJAX that required the same script would result in 10 copies of that script being loaded in memory. Hence why I thought that the only way to work with adding a script via AJAX would be to ensure that the script is only ever loaded once, and then use an event to trigger the script’s init on subsequent page loads where that script is required.

However, after doing some more testing it turns out this behaviour is only seen when you have the developer tools open in IE. Without developer tools it behaves more like other browsers (though appears to have no GC?) And so just removing all added scripts and then adding again any needed ones is not actually a problem at all.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.