javascriptdocumentfragment

Function property holding a reference to a document fragment: when to use cloneNode?


I'm having trouble understanding when a data assignment to an object is a reference and when a copy of the object is created. I thought I understood but the following example doesn't fit my simple undestanding of it.

An event handler starts off a series of steps as follows.
Promise.allSettled( [ gather_write, get_build ] ).then( test );

Promises gather_write and get_build perform unrelated functions. The first gathers some data from the DOM and writes it to the database. The second retrieves data from the database and builds a document fragment. If both fulfill, then a node in the DOM is replaced with the fragment.

It's too much code to show here, but get_build, after successfully getting the data from the database, invokes a separate function that builds the document fragment, and was returning its result as a property of the resolve object. That worked fine; and then I wanted to try to provide options to the user in the event that gather_write rejected and get_build fulfilled, which requires the temporary storing of the document fragment. So, instead of returning it and passing it back to the Promise.allSettled, it is stored in a property of the function that builds it.

In the synchronous function build the code is set up as:

function build()
  {
    let f;
    try
      {
        f = document.createFragment();
        // ...build the fragment... 
        build.html = f;
      }
     catch(e)
      { }
     finally
      { f = null; }
   } // close build

Function build has to complete before the promise get_build can resolve, after which the Promise.allSettled can be evaluated; and, if both promises fulfill, the function to replace the DOM node with the newly built fragment stored in build.html can be invoked. I thought that build.html would be a reference to the node object f and that, since f is set to null in the finally block, that would take place before all the above could complete, and when the code to use build.html was finally run it would be pointing to a null rather than the fragment. So, the assignment statement should be build.html = f.cloneNode(true).

However, the process works fine with and without using f.cloneNode. Would you please explain why? I don't want the browser to take the steps to clone the fragment if it is unnecessary, but hesitate to exclude it without understanding why it is working without cloning.

Thank you.


Solution

  • You can think of every variable name as a pointer to a memory location. Setting a variable name to null does not alter anything in what the variable used to contain; all it does is change what the variable name points to, from the prior reference (here, to a document fragment) to the new reference (null). So, when you do

    f = document.createFragment();
    build.html = f;
    

    then, no matter if/how the f variable name gets reassigned in the future, build.html will not be altered, because it continues to point to the document fragment. The only way the build.html would be altered would be if the fragment was mutated (such as assigning to a property of f before the reassignment, or calling one of the fragment's methods).

    Here's another minimal example of this behavior:

    function foo() {
      let f = { prop: 'val' };
      foo.f = f;
      f = null;
    }
    
    foo();
    
    console.log(foo.f);

    Again, here, although f gets set to null, the object { prop: 'val' } remains intact and unmutated, so foo.f gets a reference to that object, and continues to hold that reference even after f gets reassigned.

    With your code, since the fragment in memory remains in memory, and the build.html retains a reference to that fragment even after f gets reassigned, cloning the fragment is just unnecessary overhead; feel free to leave it out.

    Another way to visualize it is that your code:

    function build() {
      let f;
      try {
        f = document.createFragment();
        // ...build the fragment... 
        build.html = f;
      } catch (e) {} finally {
        f = null;
      }
    }
    

    is equivalent to

    function build() {
      let f;
      try {
        // Create the object in memory:
        <MemRef#513513> = document.createFragment();
        // Point the variable `f` to that created object:
        f = <MemRef#513513>;
        // ...build the fragment... 
        build.html = <MemRef#513513>;
      } catch (e) {} finally {
        f = <MemRef#Null>;
      }
    }