Javascript: Deleting displayed objects without references using jQuery.remove() - javascript

I have objects who's only references are the DOM elements they are tied to and I'm not sure if calling $element.remove() actually removes the reference or just the DOM element. Here's some example code.
var Foo = function() {
var constructor = function Foo() {
var col = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
var $container = $('<div class="element" style="background-color:'+col+';"></div>');
var $remove = $('<input type="button" value="Delete" />');
$container.append($remove);
$('#wrapper').append($container);
$remove.on('click', function() {
$container.remove();
});
};
return constructor;
}();
$('#addElement').on('click', function() {
new Foo();
});
And a jsfiddle for people to play around with. Click one button to add elements, click each element's "Delete" button to remove it; from the DOM at least.
It's not necessarily a problem for my current project because of the small scale, but I think it'd be really useful to know for future reference if I'm ever working on something large.
If it doesn't, is there any way to remove the objects without holding them in some array?
EDIT: Ok, so I've kinda answered my own question using the chrome heap snapshots and adding 10,000 elements to the page. Here's the important data. (I've included HTMLInputElements because the majority of what I'm inserting are those)
Initial 10,000 Removed
Memory 2.7MB 135MB 4.0MB
Objects 1,676 81,693 11,703
InputElements 1 59,995 1
So yeah, it seems that the garbage collector is smart enough to remove the objects when the DOM element referencing them is removed. Though it's not perfect.

The objects will be garbage collected as soon as you do not have a reference to the object anymore. I'm not sure if it would be considered a "best practice" to do this though. From MDN:
On the other hand, JavaScript values are allocated when things (objects, strings, etc.) are
created and "automatically" free'd when they are not used anymore.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management
The "automatically" described above in modern browsers means that objects that are considered "unreachable" will be marked for garbage collection.
Calling $.element.remove() will only remove the DOM element because it only knows about the DOM element. It's not possible that it could remove any other object associated with the DOM element because it does not know about that object.
http://api.jquery.com/remove/

Related

Searching Up the Scope vs Accessing the DOM - Speed Performance

I am learning javaScript and there are still many doubts. I've already tried to search this one but maybe I am using the wrong words. I am always delaying this but since the size of the project I am working on is becoming larger than I expected I need to be clarified.
I am aware that are costs in terms of speed in both searching up the scope and accessing the Dom. But I don't know which one is slower than the other.
Is it faster...
a) to go up the scope chain to grab the variable with the jQuery object, but go to the Dom just once
or
b) Not go up the scope chain, but go to the Dom again
var $el = $("#el");
//$el is used on this scope (and so, the question makes some sense:))
$el (...)
// some or lot's of code
function a() {
// some or lot's of code
function b(){
// some or lot's of code
function c() {
a) $el (...)
b) var $el = $("#el");
$el (...)
}
}
}
Option A is faster. (Declaring the variable on top)
When you need $("#el") more then once store it as a variable. Using the DOM multiple times is slow.
Greg Franko explains a few best practices here. See slide 10-13 for your question.
While your question is very unclear, perhaps I can answer it with a general rule of thumb:
The DOM is slowwwww. If you can avoid traversing it, do so. If you search the DOM to find a specfic node, store that reference in a variable to avoid having to search through the DOM again.
If I understanded your question, you are asking about performance accessing elements in jQuery. Here you have some tips:
Every time you have to get the reference to a jQuery object ($("#el") in your example) has a operational cost. In terms of performance, you should always try to declare the minimun necessary elements to your needs.
Accessing siblings (siblings()), parents (closest(), parent()) or children (children(), find()) is a good option if is just for a few operations, but if you are going to use that elements many times, its better reference them with a direct selector like $("#el-child") or ("#el .child").
If you declare a var like $el = $("#el") you get a reference of the node in its current state. Sometimes this node has being updated, removed, re-created, or something else, and this reference wont keep trak of them, so you have to call this $el = $("#el") again so, as I said before, it depends how your website logic works.
Note: sorry about my english, I know it's not good at all hehe

Efficiency of repeated checks with jQuery selector

Does jQuery cache selector lookups?
I am wondering whether this code
if ($("#ItemID").length) {
doSomethingWith($("#ItemID"));
}
will be significantly slower than this code
item = $("#ItemID");
if (item.length) {
doSomethingWith(item);
}
What about if you're extracting much more of the DOM e.g., $("div") rather than just $("#ItemID")? Same answer?
External references/explanation would be helpful, rather than just opinion.
According to the jQuery Learning Center, "jQuery doesn't cache elements for you. If you've made a selection that you might need to make again, you should save the selection in a variable rather than making the selection repeatedly."
This is particularly important when your selector is slow by its nature. For example, $("#foo") makes a call under the covers to document.getElementById() which should be very fast. However, a selector like $("a[rel$='thinger']") might be significantly slower on an older browser.
Remember that jQuery supports chaining, so you don't always have to introduce a variable to save the selection, you can just chain sequential calls together. For example, in this code I don't introduce a variable just to save the result of .find("img"), rather I chain three calls to prop and one to show.
clusterBlock.find("img").prop("src", clusterElt.imageUri)
.prop("alt", clusterElt.description)
.prop("id", clusterElt.imageId)
.show();
Finally, remember that a saved selection is not updated as the DOM changes. If I get all elements with class "foo" and save that in a variable, then some other piece of code adds and removes a couple of "foo" elements, then my saved selection will be missing the new elements and contain the references to the removed elements.
This
var item = $("#ItemID");
if (item.length) {
doSomethingWith(item);
}
will be faster but marginally. doSomethingWith will go to pointer where object is stored.
Turns out people do cache selectors btw

Does jQuery cache elements internally?

I know jQuery doesn’t cache collections of element, f.ex calling:
$('.myclass').html('hello');
$('.myclass').html('bye');
Will make jQuery climb the DOM twice.
But how about cached DOM nodes?
var elems = document.querySelectorAll('.myclass');
$(elems).html('hello');
$(elems).html('bye');
Will jQuery cache those internally, or will they be equally slow as the first example?
To clarify: will jQuery keep a reference to elems and cache $(elems) internally so it won’t have to apply the same $() wrapper every time?
Something like:
cache = {}
constructor = function(collection)
if collection in cache
return cache[collection]
else construct(collection)
Assuming I've understood your question correctly, then no, jQuery won't keep a reference to the selected nodes beyond the statement that uses them:
$('.myclass').html('hello'); //Select all .myclass elements, then change their HTML
$('.myclass').html('bye'); //Select all .myclass elements, then change their HTML again
If you maintain a reference to those selected nodes separately, it will be faster:
var elems = document.querySelectorAll('.myclass'); //Select all .myclass elements
$(elems).html('hello'); //Change their HTML (no need to select them)
$(elems).html('bye'); //Change their HTML (no need to select them)
The difference won't be massive (unless your DOM is very complicated) but there will be a difference:
Update
will jQuery keep a reference to elems and cache $(elems) internally so
it won’t have to apply the same $() wrapper every time?
No, it won't. As stated above, the reference to the matched set of elements will not be maintained beyond the statement to which it applies. You can improve the performance of your code by keeping a reference to jQuery objects that are used throughout, rather than selecting them again each time, or even wrapping a stored set of native DOM nodes in a jQuery object each time.
If "cache" means "keep the DOM elements in the internal collection for that jQuery object" .. then yes.
Imagine
jq = $(elementListOrSelector)
where jq[0..jq.length-1] evaluate to the respectively DOM element. For instance, jq[0] evaluates to the first DOM element represented by the jQuery object, if any. Note that this collection is not magically changed once it has been built (and the way in which it was built does not matter).
However there is no "cache" outside of simply keeping the immediate results for that particular jQuery object.

Why should y.innerHTML = x.innerHTML; be avoided?

Let's say that we have a DIV x on the page and we want to duplicate ("copy-paste") the contents of that DIV into another DIV y. We could do this like so:
y.innerHTML = x.innerHTML;
or with jQuery:
$(y).html( $(x).html() );
However, it appears that this method is not a good idea, and that it should be avoided.
(1) Why should this method be avoided?
(2) How should this be done instead?
Update:
For the sake of this question let's assume that there are no elements with ID's inside the DIV x.
(Sorry I forgot to cover this case in my original question.)
Conclusion:
I have posted my own answer to this question below (as I originally intended). Now, I also planed to accept my own answer :P, but lonesomeday's answer is so amazing that I have to accept it instead.
This method of "copying" HTML elements from one place to another is the result of a misapprehension of what a browser does. Browsers don't keep an HTML document in memory somewhere and repeatedly modify the HTML based on commands from JavaScript.
When a browser first loads a page, it parses the HTML document and turns it into a DOM structure. This is a relationship of objects following a W3C standard (well, mostly...). The original HTML is from then on completely redundant. The browser doesn't care what the original HTML structure was; its understanding of the web page is the DOM structure that was created from it. If your HTML markup was incorrect/invalid, it will be corrected in some way by the web browser; the DOM structure will not contain the invalid code in any way.
Basically, HTML should be treated as a way of serialising a DOM structure to be passed over the internet or stored in a file locally.
It should not, therefore, be used for modifying an existing web page. The DOM (Document Object Model) has a system for changing the content of a page. This is based on the relationship of nodes, not on the HTML serialisation. So when you add an li to a ul, you have these two options (assuming ul is the list element):
// option 1: innerHTML
ul.innerHTML += '<li>foobar</li>';
// option 2: DOM manipulation
var li = document.createElement('li');
li.appendChild(document.createTextNode('foobar'));
ul.appendChild(li);
Now, the first option looks a lot simpler, but this is only because the browser has abstracted a lot away for you: internally, the browser has to convert the element's children to a string, then append some content, then convert the string back to a DOM structure. The second option corresponds to the browser's native understanding of what's going on.
The second major consideration is to think about the limitations of HTML. When you think about a webpage, not everything relevant to the element can be serialised to HTML. For instance, event handlers bound with x.onclick = function(); or x.addEventListener(...) won't be replicated in innerHTML, so they won't be copied across. So the new elements in y won't have the event listeners. This probably isn't what you want.
So the way around this is to work with the native DOM methods:
for (var i = 0; i < x.childNodes.length; i++) {
y.appendChild(x.childNodes[i].cloneNode(true));
}
Reading the MDN documentation will probably help to understand this way of doing things:
appendChild
cloneNode
childNodes
Now the problem with this (as with option 2 in the code example above) is that it is very verbose, far longer than the innerHTML option would be. This is when you appreciate having a JavaScript library that does this kind of thing for you. For example, in jQuery:
$('#y').html($('#x').clone(true, true).contents());
This is a lot more explicit about what you want to happen. As well as having various performance benefits and preserving event handlers, for example, it also helps you to understand what your code is doing. This is good for your soul as a JavaScript programmer and makes bizarre errors significantly less likely!
You can duplicate IDs which need to be unique.
jQuery's clone method call like, $(element).clone(true); will clone data and event listeners, but ID's will still also be cloned. So to avoid duplicate IDs, don't use IDs for items that need to be cloned.
It should be avoided because then you lose any handlers that may have been on that
DOM element.
You can try to get around that by appending clones of the DOM elements instead of completely overwriting them.
First, let's define the task that has to be accomplished here:
All child nodes of DIV x have to be "copied" (together with all its descendants = deep copy) and "pasted" into the DIV y. If any of the descendants of x has one or more event handlers bound to it, we would presumably want those handlers to continue working on the copies (once they have been placed inside y).
Now, this is not a trivial task. Luckily, the jQuery library (and all the other popular libraries as well I assume) offers a convenient method to accomplish this task: .clone(). Using this method, the solution could be written like so:
$( x ).contents().clone( true ).appendTo( y );
The above solution is the answer to question (2). Now, let's tackle question (1):
This
y.innerHTML = x.innerHTML;
is not just a bad idea - it's an awful one. Let me explain...
The above statement can be broken down into two steps.
The expression x.innerHTML is evaluated,
That return value of that expression (which is a string) is assigned to y.innerHTML.
The nodes that we want to copy (the child nodes of x) are DOM nodes. They are objects that exist in the browser's memory. When evaluating x.innerHTML, the browser serializes (stringifies) those DOM nodes into a string (HTML source code string).
Now, if we needed such a string (to store it in a database, for instance), then this serialization would be understandable. However, we do not need such a string (at least not as an end-product).
In step 2, we are assigning this string to y.innerHTML. The browser evaluates this by parsing the string which results in a set of DOM nodes which are then inserted into DIV y (as child nodes).
So, to sum up:
Child nodes of x --> stringifying --> HTML source code string --> parsing --> Nodes (copies)
So, what's the problem with this approach? Well, DOM nodes may contain properties and functionality which cannot and therefore won't be serialized. The most important such functionality are event handlers that are bound to descendants of x - the copies of those elements won't have any event handlers bound to them. The handlers got lost in the process.
An interesting analogy can be made here:
Digital signal --> D/A conversion --> Analog signal --> A/D conversion --> Digital signal
As you probably know, the resulting digital signal is not an exact copy of the original digital signal - some information got lost in the process.
I hope you understand now why y.innerHTML = x.innerHTML should be avoided.
I wouldn't do it simply because you're asking the browser to re-parse HTML markup that has already been parsed.
I'd be more inclined to use the native cloneNode(true) to duplicate the existing DOM elements.
var node, i=0;
while( node = x.childNodes[ i++ ] ) {
y.appendChild( node.cloneNode( true ) );
}
Well it really depends. There is a possibility of creating duplicate elements with the same ID, which is never a good thing.
jQuery also has methods that can do this for you.

Javascript remove all descendants of a div/node

I've been working on some memory leaks in Javascript for some time now. I've come to a point where I feel the only solution for at least some of the leaks is to remove a given div and then ALL of it's descendants. Meaning children, grandchildren, etc.
Now I'm under the impression I need a recursive function if I want to do this, but I'm still somewhat new to Javascript. I don't even know if recursion works the same way in Javascript as I'm used to.
I'm open to any ideas on how to do such a thing, perhaps I'm way off the mark with the idea of recursion.
This will quickly destroy the node and all its children:
var _dummyNode;
var destroy = function(node){
if(!_dummyNode) _dummyNode = document.createElement('div');
_dummyNode.appendChild(node.parentNode.removeChild(node));
_dummyNode.innerHTML = "";
}
destroy(nodeToDestroy);
It creates a dummy DIV that never gets appended to the doc so doesn't show. The node is removed from its parent and appended to the dummy. It's then wiped out with innerHTML.
However - this will fail if you still have variable references to the nodes. You should use a good Ajax library and only use the appropriate addEventListener to attach events. You shouldn't do:
<div onclick="myFunction();">click me</div>
Because it's not a good separation of concerns, it's hard to maintain, it doesn't properly pass events, and you can only attach one method. And if you do it that way you essentially need to do your own garbage collecting.
I don't know if I fully understand your desired end result, but setting the innerHTML of the div to a blank screen effectively gets rid of all the child nodes:
document.getElementById('test').innerHTML = '';
You could do something like:
function removeChildren(elem){
for(var i in elem.children){
removeChildren(elem.children[i])
elem.removeChild(elem.children[i])
}
}
OR
function removeChildren(elem){
while (elem.hasChildNodes()) {
removeChildren(elem.lastChild)
elem.removeChild(elem.lastChild);
}
}
The latter option is probably better.

Categories

Resources