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.
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>;
}
}