Chanyoung Joo
Chanyoung Joo

Reputation: 48

potential memory issue with dynamically injecting jquery html() that has scripts

I have the following pseudo-SPA like structure:

HTML layouts example

main.html

<html>
    <!--main content above...-->
    <!--dynamic content to replace following div...-->
    <div id="target"></div>
    
    <button onclick="replaceHtml('/sample/fragmentA')">fragmentA</button>
    <button onclick="replaceHtml('/sample/fragmentB')">fragmentB</button>
    <button onclick="replaceHtml('/sample/fragmentC')">fragmentC</button>

</html>
<script>
function replaceHtml(url){
    $.ajax({
        url:url
        , success: function(data){
            $("#target").html(data);
        }
    })
}
</script>

fragmentA.html

<div>
    <!--some content to replace target...-->
</div>
<script>
var a = "a";
function somefunction_a(){
    //do something in a...
}
</script>

fragmentB.html

<div>
    <!--some content to replace target...-->
</div>
<script>
var b = "b";
function somefunction_b(){
    //do something in b...
}
</script>

Requirements

Question

  1. if users continue to load fragments into main.html (which they will), wouldn't the client-side be flooded with variables and functions that are possibly no longer used but are not released from the memory? - maybe slowing down client-side rendering or allowing memory leak in return?
  2. and if so, is there a way to release the variables and functions created by html() from memory before a new fragment is loaded?(while keeping vars and functions in main.html)

Upvotes: 0

Views: 91

Answers (1)

traktor
traktor

Reputation: 19301

To begin here's a test page to get a handle on some of the issues (modify as desired and test in a browser to get details of document.scripts).

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>$fragTest</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
Target:
<div id="target"></div>
<hr>
<button type="button" onclick="loadFragA()">frag a</button><button type="button" onclick="loadFragB()">frag b</button>
<button type="button" onclick="test()">test</button>

<script text/javascript>"use strict";
function loadFragA(){

let html = 'FragmentA\n<script>\nvar a = "a";\nfunction something_a() {\n   alert("something_a()");\n}\n<\/script>\n';
$("#target").html(html);
}

function loadFragB(){
let html = 'FragmentB\n<script>\nvar a = "b";\nfunction something_b() {\n   alert("something_b()");\n}\n<\/script>\n';
$("#target").html(html);
}

function test() {
   console.log( "typeof a: %s, a: '%s': typeof something_a: %s, typeof something_b: %s, doc.scripts: ",
    typeof a, typeof a == "undefined" ? "undefined" : a,
    typeof b, typeof b == "undefined" ? "undefined" : b,
    typeof something_a, typeof something_b, document.scripts)
}
</script></body></html>


  1. Running the test immediately demonstrates that the functions and global variables remain in place. If redeclared (like var a = 'b'; in fragmentB) the new declaration replaces the old.

    • you can't declare global variables using let or const or class in fragments because reloading the fragment will attempt to redefine the identifier after the keyword which is not permitted.
  2. Global variables are global. main.html should define what individual variables or a name space object that are reserved for inter-fragment communication, and provide a set of API endpoints that main provides for fragment use.

    • Fragments should be required to declare fragment variables in function scope, meaning in an IEFE or in a function that gets called when the fragment is loaded (more on this below).

Using the test code above showed that the number of script elements in the DOM did not go up as jQuery loaded new fragments or reloaded one previously loaded (in version 3.6.0 at least). Without looking into what jQuery is doing, to me this means you don't have to take care of removing script elements declared in fragments.

Regarding the choice between using an anonymous immediately executed function to perform set up in a fragment, the alternative would be to use the same setup function name in each fragment and call it after loading the fragment. During fragment load, the new version of the named setup function replaces the old function because JavaScript allows functions to be re-declared. Any function scope variables in the old setup function object will get garbage collected from memory.

If you implement a named setup function called from main.html, then you might as well implement a corresponding destroy function that can be called immediately before fragment replacement.


Design Concept of a main.html API

This outline should be read as details of a concept, not a directive to adopt the concept or its rules without further research or appropriate consideration:

  1. Fragment code must NOT create global variables.
  2. main.html exposes whatever API methods or data it provides in global scope, such as replaceHTML and possibly a variable named destroyFragment;
  3. main.html also provides a namespace object for inter fragment communication called, say, fragmentNS, which it does not monitor.
  4. Fragments should create a single function named setupFragment (or similar) to be called after the fragment is loaded. This function contains all fragment code and variables.
  5. Fragment code may set global variable destroyFragment (or similar) to get called for cleanup before the fragment is replaced.
  6. replaceHTML gets expanded to
  • Call destroyFragment if it is a function,
  • Set global variable destroyFragment to null,
  • load the fragment html using the ajax call,
  • in the success handler add a new line to call setupFragment.

Upvotes: 1

Related Questions