Why does `.getElementById` not work on node?

I believe that if .getElementById were available on all nodes, there would be two main advantages:

  • You can select nodes that do not belong to the document.

    For example, I would like to do something like

     function foo(html){ var el = document.createElement('div'); el.innerHTML = html; var target = el.getElementById('target'); /* Do something with `target` */ } 

    But I can not, because I get TypeError: el.getElementById is not a function .

    Then I do not want to use the class instead of id, I have to do

     function foo(html){ var el = document.createElement('div'); el.innerHTML = html; document.body.appendChild(el); var target = document.getElementById('target'); document.body.removeChild(el); /* Do something with `target` */ } 

    But the document may already have an element with id="target" . Then I have to do

     function foo(html){ var iframe = document.createElement('iframe'); iframe.onload = function(){ var doc = iframe.contentDocument || iframe.contentWindow.document, el = document.createElement('div'); el.innerHTML = html; doc.body.appendChild(el); var target = doc.getElementById('target'); document.body.removeChild(iframe); /* Do something with `target` */ }; iframe.src = 'about:blank'; document.body.appendChild(iframe); } 

    But the code above does not work if I want foo return something related to html , because the main code is fired after the onload .

  • This can increase productivity if there are a lot of elements in the document, and you know that the element you are looking for is a descendant of the element that you already have in the variable

    For example, if I have a document with the following structure:

     <body> <div id="div-1"> <div id="div-1-1"> <div id="div-1-1-1"> ... </div> <div id="div-1-1-2"> ... </div> ... </div> <div id="div-1-2"> <div id="div-1-2-1"> ... </div> <div id="div-1-2-2"> ... </div> ... </div> ... </div> <div id="div-2"> <div id="div-2-1"> <div id="div-2-1-1"> ... </div> <div id="div-2-1-2"> ... </div> ... </div> <div id="div-2-2"> <div id="div-2-2-1"> ... </div> <div id="div-2-2-2"> ... </div> ... </div> ... </div> ... </body> 

    And I...

     var el1 = document.getElementById('div-9999999'), el2 = document.getElementById('div-9999999-1-2'), el3 = document.getElementById('div-9999999-1-2-999999'), el4 = document.getElementById('div-9999999-1-2-999999-1-2-3-4-5'); 

    ... it can be much slower than

     var el1 = document.getElementById('div-9999999'), el2 = el1.getElementById('div-9999999-1-2'), el3 = el2.getElementById('div-9999999-1-2-999999'), el4 = el3.getElementById('div-9999999-1-2-999999-1-2-3-4-5'); 

    (Of course, this example is a simplification, in which case using .childNodes[] or .children[] will be much better);

Then why is .getElementById not working on node? I see no disadvantages, only advantages

+4
source share
3 answers

The main reason is efficiency.

At the document level, a single identifier reference table must be maintained, and any changes to the document require O(1) changes. this table. To implement it at the node level, an equivalent table needs to be maintained for each node, and updates for any given element, regardless of whether it is attached to the document, would have to bubble through each of its parent nodes. It eats a lot of memory very quickly and takes a long time to update the DOM.

Another important thing is to note that (from a Javascript point of view, at least) each element belongs to a document (or a fragment of a document). Therefore, the argument โ€œbut I can have duplicate identifiers, if only one of them is attached to the documentโ€ does not really add up - it is only possible to manage this based on nodes on the DOM, when you take this into account.

+4
source

Regarding the problems described in the first example / requirement. Because getElementById exists only in the document node, since it uses caches that are provided only by the node tree as part of document . You have three options for finding a node tree that is not bound to document . Everyone suffers from 0 (log n), since they do not use document caching, this is almost there (you tried to use iFrame ).

One: node recursive walker.

The advantage is that it is a cross-browser interface

The downside is that it will always be 0 (log n) - if used on document

Javascript

 function getElementById(node, id) { if (node.id === id) { return node; } var target; node = node.firstChild; while (node) { target = getElementById(node, id); if (target) { return target; } node = node.nextSibling; } return undefined; } function foo(html) { var el = document.createElement("div"); el.innerHTML = html; var target = getElementById(el, "target"); /* Do something with `target` */ if (target) { console.log(target); } } foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>'); 

On jsfiddle

Two: using querySelector , which is available for each element.

The advantage is that it requires less code.

The downside is that it requires IE8 + (and IE8 itself has limitations on CSS request)

Javascript

 function getElementById(node, id) { return node.querySelector("#" + id); } function foo(html) { var el = document.createElement("div"); el.innerHTML = html; var target = getElementById(el, "target"); /* Do something with `target` */ if (target) { console.log(target); } } foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>'); 

Jsfiddle on

Three - use TreeWalker

The downside is that it requires IE9 +, is less understood (often forgotten) than the previous ones, and requires more code than querySelector

Javascript

 function getElementById(node, id) { return document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT, { acceptNode: function (n) { if (n.id === id) { return NodeFilter.FILTER_ACCEPT; } } }, false).nextNode(); } function foo(html) { var el = document.createElement("div"); el.innerHTML = html; var target = getElementById(el, "target"); /* Do something with `target` */ if (target) { console.log(target); } } foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>'); 

Jsfiddle on

Now about the performance of these methods see this jsperf

Note. The execution of the three methods will change dramatically if the nodes are part of a document !

As for your second wish, what you described is a dumb point due to the nature of document caches.

Update: If asynchrony is not a problem for your requirement, you can do it using iFrame .

The advantage is that you can now use getElementById

The disadvantage is the huge overhead of creating and destroying an iFrame

Javascript

 var getElementById = (function () { var parent = document.body || document.documentElement, javascript = "javascript"; return function (node, id, func) { var iframe = document.createElement("iframe"); iframe.style.display = "none"; iframe.src = javascript + ":"; iframe.onload = function () { iframe.contentWindow.document.body.appendChild(node); func(iframe.contentWindow.document.getElementById(id)); parent.removeChild(iframe); }; parent.appendChild(iframe); }; }()); function foo(html) { var el = document.createElement("div"); el.innerHTML = html; getElementById(el, "target", function (target) { /* Do something with `target` */ if (target) { console.log(target); } }); } foo('<div id="nottarget1"><div id="nottarget2"><div id="nottarget3"><div id="nottarget4"><div id="target">Target</div></div></div></div></div>') 

;

Jsfiddle on

+3
source

"One could select nodes that do not belong to the document."

Yes, but the method uses the fact that the identifier is unique in the document to optimize performance. This is not possible if the item is not in the document.

"It can improve performance if there are a lot of elements in the document, and you know that the element you are looking for is a descendant of the element that you already have in the variable."

No, because it already uses a method that does not have to look through all the elements to find the identifier. Passing through the elements would simply slow down the work.

0
source

Source: https://habr.com/ru/post/1490081/


All Articles