So this is my question: Why would calling a method be faster then retrieving something from memory?
Noticed when an id attribute is specified on a DOM element, user agents automatically attach the element's reference on their global scope.
Since user agents already reference all elements, which have their id attribute specified, why would I need to use document.getElementById("")?
Within an application, I would:
//Retrieving the value, I could possibly write this two way.
<script>
var fromGlobalScope = myElement.value;
var documentGetById = document.getElementById("myElement").value;
</script>
<input id="myElement" value="someValue" />
Doing some research, it is supported by all major browser, but their may some browser that do not support, which will break.
However, I could simply write:
<script>
//See if the element is on the global scope.
var fromGlobalScope = myElement ||document.getElementById("myElement");
</script>
I believe patterned correctly, I can automatically have references to all elements that have an id attribute. I don't have to call document.getElementById();
Using an resident property and I wouldn't have to walk the DOM, would think there would be a good performance benefit.
I created a jsPerf to see the benefit: enter link description here
My surprise was using document.getElementById() was a lot faster?
So this is my question: Why would calling a method be faster then retrieving something from memory?
Using document.getElementById, I would be calling a method that may or may not walk the DOM. At least, I will be calling an address for the value.
With a property on the global scope that should be quickly available as it is placed in some memory location.
I have include jsPerf results below:
I created another jsPerf with another thought:
explicitly setting a property on the window object
However, I still believe learning why can help with the mechanics that are at play, which may result in something helpful.
From HTML5 spec
5.2.4 Named access on the Window object
The Window interface supports named properties. The supported property
names at any moment consist of the following, in tree order, ignoring
later duplicates:
the browsing context name of any child browsing context of the active document whose name is not the empty string,
the value of the name content attribute for all a, applet, area, embed, form, frameset, img, and object elements in the active document that have a non-empty name content attribute, and
the value of the id content attribute of any HTML element in the active document with a non-empty id content attribute.
So the browser will probably walk the DOM tree to resolve a named property. In contrast getElementById() just needs to look the id up in (say) a hash map.
While the browser could maintain a hash map of the first matching element to that algorithm, maintaining that map would impose a performance penalty that would rarely pay for itself. In contrast the browser is looking up elements by their id constantly, so it pays to keep the id map.
Related
I'm learning Javascript right now, and attempting to change the text title of a particular tab. It's actually part of a larger Shiny dashboard project, but I want to add some custom functionality to a few tabs. Below are the tabs in question:
Simple enough. I first access my tabs in my Javascript file:
var tabScrub2 = $(document).find('[data-value="scrubTab2"]');
console.log(tabScrub2);
When I use Firefox's developer console, I see that the tab is an object:
Moreover, it looks like I need to change the innerText property of 0, whatever this is, since that corresponds to the title of my tab (the innerText of 1 corresponds to the text inside scrubTab2). However, I'm not familiar with the actual object type being returned here:
Simply put, how the heck do I access and manipulate properties from this? And am I actually accessing an array? When I type in
var scrub2 = tabScrub2["1"];
console.log(scrub2);
I get an HTML element. I'm seen the a element in CSS and jQuery, but am not super familiar with how to manipulate its properties programmatically? How do I go about accessing and manipulating the innerText properties of this via Javascript? For instance, how would I hide scrubTab2, or change its title to something else?
The first object you're seeing is jQuery's wrapper around the real DOM elements. It's not an actual array, but it does contain all of the elements that matched your query under zero-indexed properties (e.g. "0" and "1") which allows you to access to them via an array-like API (e.g. tabScrub[1]).
Your method of grabbing a node using tabScrub2["1"] is correct (see this question in the jQuery FAQ). It's more likely to see that done with a numeric key though (i.e. tabScrub[1]) because that matches the way you would access an element in a normal array.
As far as manipulating properties of the DOM node, the DOM's API is notoriously inconsistent and quirky (hence the need for things like jQuery in the first place). However, for your use case you can just assign a string to the innerText property directly (e.g. tagScrub2[1].innerText = "Tab title"). MDN is a great resource if you're looking for reference material on other parts of the DOM.
A side note: if you're looking for a specific element you should use a query that will only match that element. It's generally a bad sign if you're grabbing extra elements and then accessing the element you want at a key other than 0. If you're doing this then your code depends on other (potentially unrelated) nodes in the DOM existing before your node, and if/when you change those nodes your original code will break.
Just use jQuery eq method to get the relevant object index from the array.
For an example
//Query and get first element.
var tabScrub2 = $(document).find('[data-value="scrubTab2"]:eq(0)');
//Hide
tabScrub2.hide();
//Change title
tabScrub2.attr("title", "New Title Text");
Lean more about jQuery eq here.
https://api.jquery.com/eq/
Since you use jquery selectors tabScrub2[0] returns the native DOM element instead of another jQuery object. Therefore the hide function won't work in that object since the native DOM element doesn't implement such type of functionality for an element. That's why you have to use jQuery pseudo selector as above. Because hide will only work with a jQuery object.
I have previously mentioned in this SO about the funny behavior for IE6/7 (and some versions of Opera) in that document.getElementById can find an element whose name attribute is defined but not the id attribute, such that
function f() {
document.getElementById("a1").value = ...;
}
...
<input name="a1" ...></input>
actually works in these versions.
Searching through the net I found this bug report by Chris Bloom, in which a user named Milo van der Leij points out the following (as referred by him in this w3c spec):
In their defense: "The id and name attributes share the same name space."
What does it mean that the id and name attributes share the same namespace? Why would this condition be sufficient for IE6/7/Opera implement this behavior in their JS engine?
The term "same namespace" means that names and ids are not completely separate. You can use the same name and id on one particular object, but you cannot use name="foo" on one object and id="foo" on another object. That creates a conflict.
It's just the way those browsers decided to implement things. There's also a global variable for each element with an id that contains the dom element. That's just the way they implemented things. It wasn't standard, it isn't the way things are done in more modern browsers (except for some backward compatibility).
Use id values for any DOM elements you want to retrieve. Use name values for server identification in posted forms.
Your code will have no conflicts between names and ids if you don't use an id on one object and the same name on another object, and there generally isn't an issue with giving a particular element the same name and id.
I've seen some JavaScript code to access HTML elements like this: elementID.innerHTML, and it works, though practically every tutorial I searched for uses document.getElementById(). I don't even know if there's a term for the short addressing.
At first I thought simplistically that each id'ed HTML element was directly under window but using getParent() shows the tree structure is there, so it didn't matter that elements I wanted were nested. I wrote a short test case:
http://jsfiddle.net/hYzLu/
<div id="fruit">Mango<div id="color">red</div></div>
<div id="car">Chevy</div>
<div id="result" style="color: #A33"></div>
result.innerHTML = "I like my " + color.innerHTML + " " + car.innerHTML;
The "short" method looks like a nice shortcut, but I feel there is something wrong with it for it practically not appearing in tutorials.
Why is document.getElementById() preferred, or may be even required in some cases?
Why shouldn't I access elements more “directly” (elemId.innerHTML)
Because, according to the others in this thread, referencing arbitrarily by id name is not fully supported.
So, what I think you should be doing instead is store their selections into a var, and then reference the var.
Try instead
var color = document.getElementById('color');
color.innerHTML = 'something';
The reason why this would be a good thing to do is that performing a lookup in the DOM is an expensive process, memory wise. And so if you store the element's reference into a variable, it becomes static. Thus you're not performing a lookup each time you want to .doSomething() to it.
Please note that javascript libraries tend to add shim functions to increase general function support across browsers. which would be a benefit to using, for example, jquery's selectors over pure javascript. Though, if you are in fact worried about memory / performance, native JS usually wins speed tests. (jsperf.com is a good tool for measuring speed and doing comparisons.)
It's safer I guess. If you had a variable named result in the same context that you are doing result.HTML I'm pretty sure the browser will throw a wobbler. Doing it in the way of document.getElementById() in this instance would obviously provide you with the associated DOM element.
Also, if you are dynamically adding HTML to the page I may be wrong, but you could also encounter unexpected behaviour in terms of what result is :)
Also I will add that not all ID's can have values that will not work as variable names. For instance if your ID is "nav-menu".
Although I suppose you could write window["nav-menu"].innerHTML
Which makes me think, what happens if you create a window level variable with the same name as an ID?
Checkout this jsfiddle (tested in chrome): http://jsfiddle.net/8yH5y/
This really seems like a bad idea altogether. Just use document.getElementById("id") and store the result to a variable if you will be using the reference more than once.
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.
This might be crazy but it intriguing me for quite some time :)
I would like to know how a javascript variable can bind itself do the DOM after it is appended to the body, for example?
var p = document.createElement('p');
p.innerHTML = 'Hello World';
document.body.appendChild(p);
So now I have this p variable which contains an exact reference of that specific paragraph no matter where it is located inside the body.
p.innerHTML = 'new content';
will easily find the paragraph and change its value
So my question is...how this binding is made?
what If I want to re-create that after the variable is gone?
is there any way to attach that again without having to run through the DOM and find it?
I was thinking if somehow each node inside the DOM have its specific identifier that is not the id attribute but some kind of UUID that can be referred later on?
like:
console.log(p.localName); //aoi12e2kj2322444r4t
p = null;
so I can still recover that paragraph node thought this uuid?
In this environment I wouldn't have access to any external node attribute, such name, id, data, etc..
So I am quite curious to know how this binding is created between variable and DOM node?
I believe that it changes depending on the browser your using. There's no standard way to do so. Currently you either use the id or iterate over the dom until you reach the element you want.
The binding is created on the first line, where you assign the result of document.createElement to p. This is no different from any other time you assign something to a variable, which always binds the variable name to the value. As far as the script is concerned, there is no other binding occurring. The p is an HTMLElement, and that's all of the element that's exposed.
Note that for p.innerHTML = 'new content';, the element doesn't have to be found because p already refers to the element. That's what the DOM does: it exposes documents and document elements.
If you later want another reference to the same element, you'll have to use DOM methods (such as getElementById) to find it. That's what they're there for.
As for how the DOM exposes elements, that's implemented internally and varies from browser to browser or library to library (since the DOM isn't used just in browsers).