Can dynamically loaded JavaScript be unloaded?
Asked Answered
P

7

12

I am writing a web application that has a static outer "shell" and a dynamic content section. The dynamic content section has many updates as users navigate the system. When a new content block is loaded, it may also optionally load another JavaScript file. In the name of good housekeeping, I remove script blocks from the DOM that apply to old content blocks, since that JavaScript is no longer needed.

The problem comes next, when I realized that although I have removed the <script> element from the DOM, the JavaScript that was previously evaluated is still available for execution. That makes sense of course, but I'm worried that it may cause a memory leak if the users navigate to a lot of different sections.

The question then, is should I be worried about this situation? If so, is there a way to force the browser to cleanup stale JavaScript?

Prater answered 28/8, 2009 at 13:11 Comment(0)
L
14

<theory>You could go with a more object-oriented approach, and build the model in a way that each block of javascript blocks come in as their own objects, with their own methods. Upon unloading it, you simply set that object to null.</theory>

Leathers answered 28/8, 2009 at 13:14 Comment(0)
K
9

(This is fairly off-the-cuff.)

Memory use is indeed an issue you need to be concerned with in the current browser state of the art, although unless we're talking about quite a lot of code, I don't know that code size is the issue (it's usually DOM size, and leftover event handlers).

You could use a pattern for your loadable modules that would make it much easier to unload them en mass -- or at least, to let the browser know it can unload them.

Consider:

window.MyModule = (function() {

    alert('This happens the moment the module is loaded.');

    function MyModule() {

        function foo() {
            bar();
        }

        function bar() {
        }

    }

    return MyModule;
})();

That defines a closure that contains the functions foo and bar, which can call each other in the normal way. Note that code outside functions runs immediately.

Provided you don't pass out any references to what's inside the closure to anything outside it, then window.MyModule will be the only reference to that closure and its execution context. To unload it:

try {
    delete window.MyModule;
}
catch (e) {
    // Work around IE bug that doesn't allow `delete` on `window` properties
    window.MyModule = undefined;
}

That tells the JavaScript environment you're not using that property anymore, and makes anything it references available for garbage collection. When and whether that collection happens is obviously implementation-dependent.

Note that it will be important if you hook event handlers within the module to unhook them before unloading. You could do that by returning a reference to a destructor function instead of the main closure:

window.MyModule = (function() {

    alert('This happens the moment the module is loaded.');

    function foo() {
        bar();
    }

    function bar() {
    }

    function destructor() {
        // Unhook event handlers here
    }

    return destructor;
})();

Unhooking is then:

if (window.MyModule) {
    try {
        window.MyModule();
    }
    catch (e) {
    }
    try {
        delete window.MyModule;
    }
    catch (e) {
        // Work around IE bug that doesn't allow `delete` on `window` properties
        window.MyModule = undefined;
    }
}
Kamin answered 28/8, 2009 at 13:23 Comment(0)
S
3

If you save the evaluated code in namespaces, such as:

var MYAPP = {
    myFunc: function(a) { ... }
}

"Freeing" the whole thing should be as simple as setting MYPP to some random value, ala

MYAPP = 1

This does depend on there being no other means of referencing the variable, which isn't trivial

Stadia answered 28/8, 2009 at 13:15 Comment(3)
Why not delete MYAPP or MYAPP = undefined?Sideshow
Not only the variable itself, but also any closures that might be created be the functions inside.Nephew
@txwinker: Yes, I guess I felt that was implied. For this to work, there must be no references to neither MYAPP, nor anything inside it. And there's some finicky details when it comes to leaking memory, and IE. @Dykam: No reason for not going undefined, but I avoided delete, since I don't have alot of experience with it, and a "1" shouldn't put a huge memory strain on whatever the OP may be doing.Stadia
P
1

How about loading the JS files into an iframe? Then (in theory, never tested it myself) you can remove the iframe from the DOM and remove the "memory" it's using.

I think... or I hope...

Plummer answered 28/8, 2009 at 13:16 Comment(0)
C
1

If you are worried about memory leaks then you will want to make certain that there is no event handlers in the code you want removed referring to the still existing dom tree.

It may be that you need to keep a list of all event handlers your code added, and before unloading, go through and remove the event handlers.

I have never done it that way, I always worry about when I remove nodes that there is still a reference.

Here is a good article on javascript memory leaks: http://javascript.crockford.com/memory/leak.html

Clamshell answered 28/8, 2009 at 13:20 Comment(0)
C
0

JavaScript interpreters have garbage collectors. In other words, if you don't reference anything, it won't be keeping them around.

One of the reasons why it is good to use JSON with a callback function (JSONP).

example, if you HTTP response for each JS is:

callback({status: '1', resp: [resp here..]});

And if callback() does not create a reference to the JSON object passed in as an argument, it will be garbage collected after the function completes.

If you really need to make a reference, then you probably need that data around for some reason - otherwise you would/should NOT have referenced it in the first place.

The methods mentioned to namespace objects just creates a reference that will be persisted until the reference count comes to 0. In other words, you have to track every reference and delete it later, which can be hard when you have closures and references from DOM lying around. Just one reference will keep the object in memory, and some simple operations may create references without you realizing it.

Cairn answered 28/8, 2009 at 16:51 Comment(0)
K
0

Nice discussion. Clears up a lot of things. I have another worry, though.

If I bind window.MyModule.bar() to an event, what happens if the event accidentally gets triggered after window.MyModule is deleted? For me, the whole point of namespacing and separating js into dynamically loaded modules is to avoid triggering event handlers cross-module by mistake.

For example, if I do (excuse my jQuery):

$('.some-class').click(window.MyModule.bar);

What happens if I delete window.MyModule, load another module, and click on an element which accidentally has a class called some-class?

Konstance answered 9/2, 2011 at 10:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.