javascript 为什么`.getElementById` 在节点上不起作用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17508079/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 08:35:06  来源:igfitidea点击:

Why doesn't `.getElementById` work on a node?

javascriptdom

提问by Oriol

I thing that if .getElementByIdwere available on all nodes, there would be two main advantages:

我认为,如果.getElementById在所有节点上都可用,将有两个主要优点:

  1. It could be possible to select nodes which don't 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't because I get TypeError: el.getElementById is not a function.

    Then, I don't want to use a class instead of an id, I must 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 could already have an element with id="target". Then, I should 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 doesn't work if I want footo return something related with html, because the main code runs after, with the onloadevent.

  2. It could increase the performance, if the document has lots of elements and you know that the element you are searching is a descendant of an element that you already have in a 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 do...

    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 could 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, and in this case using .childNodes[]or .children[]would be much better);

  1. 可以选择不属于文档的节点。

    例如,我想做类似的事情

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

    但我不能,因为我得到TypeError: el.getElementById is not a function.

    然后,我不想使用类而不是 id,我必须这样做

    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` */
    }
    

    但是文档可能已经有一个带有id="target". 那么,我应该做

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

    但是,如果我想foo返回与 相关的内容html,则上面的代码不起作用,因为主代码在onload事件之后运行。

  2. 如果文档有很多元素并且您知道要搜索的元素是变量中已有元素的后代,则它可以提高性能

    例如,如果我有一个具有以下结构的文档:

    <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>
    

    而我...

    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');
    

    ...它可能比

    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');
    

    (当然,这个例子是一个简化,在这种情况下使用.childNodes[]or.children[]会更好);

Then, why doesn't .getElementByIdwork on a node? I don't see any disadvantage, only advantages

那么,为什么.getElementById在一个节点上不起作用呢?我看不出有什么缺点,只有优点

采纳答案by DaveRandom

The primary reason is efficiency.

首要原因是效率。

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

在文档级别,需要维护单个 ID 引用表,对文档的任何更改都需要O(1)修改此表。为了在节点级别实现它,需要为每个节点维护一个等效的表并且对任何给定元素的更新,无论它是否附加到文档,都需要通过其每个父节点进行冒泡。这会很快占用大量内存,并且需要很长时间来更新 DOM。

Another important thing to note is that (from a Javascript point of view, at least) everyelement is owned by a document (or document fragment). Therefore the argument of "but I can have duplicated IDs as long as only one of them is attached to the document" doesn't really stack up - it's onlypossible to manage this based on the nodes on the DOM when you take this into account.

另一个需要注意的重要事情是(至少从 Javascript 的角度来看)每个元素都归一个文档(或文档片段)所有。因此,“但我可以有重复的 ID,只要其中一个附加到文档”的论点并没有真正叠加 -当您考虑到这一点时,只能根据 DOM 上的节点来管理它.

回答by Xotic750

Regarding the problems that you have described in your first example/requirement. As getElementByIdonly exists on the documentnode, because it make use of caches that are only provided by a node tree being part of document. You have three choices for searching a node tree that is not attached to the document. All suffer 0(log n) as they are not taking advantage of the documentcaches, there is little or no way around this (you tried with an iFrame).

关于您在第一个示例/要求中描述的问题。由于getElementById仅存在于document节点上,因为它使用仅由作为document. 您可以通过三种选择来搜索未附加到document. 所有人都遭受 0(log n) 因为他们没有利用document缓存,几乎没有办法解决这个问题(您尝试过使用iFrame)。

One: A recursive node walker.

一:递归节点walker。

Advantage is that this is cross-browser friendly

优点是这是跨浏览器友好的

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

缺点是它将始终为 0(log n) - 如果用于 document

Javascript

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

? 在jsfiddle 上

Two: using querySelectorwhich is available per element.

二:使用querySelector每个元素可用的。

Advantage is that it requires less code

优点是需要更少的代码

Disadvantage is that it requires IE8+ (and IE8 itself has limitations on the CSS query)

缺点是需要IE8+(而且IE8本身对CSS查询有限制)

Javascript

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>');

On jsfiddle

jsfiddle 上

Three is to use TreeWalker

三是使用 TreeWalker

Disadvantages are that it requires IE9+, is less understood (often forgotten) than the previous methods, and requires more code than querySelector

缺点是需要IE9+,比前面的方法理解的少(经常忘记),需要的代码比前面的多 querySelector

Javascript

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>');

On jsfiddle

jsfiddle 上

Now, regarding performance of these methods, please see this jsperf

现在,关于这些方法的性能,请参阅此jsperf

Note: The perfomance of the three methods will alter dramatically if the nodes are part of the document!

注意:如果节点是document!

Regarding your second desire, what you have described is a mute point due to the nature of documentcaches.

关于您的第二个愿望,由于document缓存的性质,您所描述的是静音点。

Update:If being asynchronous is not a problem for your requirement, then you can do it with an iframelike so.

更新:如果异步对于您的要求来说不是问题,那么您可以使用iframe类似的方法来实现。

Advantage is that you can now use getElementById

优点是你现在可以使用 getElementById

Disadvantage is the huge overhead of creating and destroying the iframe

缺点是创建和销毁 iframe

Javascript

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>')

;

;

On jsfiddle

jsfiddle 上

回答by Guffa

"It could be possible to select nodes which don't belong to the document."

“可以选择不属于文档的节点。”

Yes, but the method uses the fact that the id is unique in the document to optimise the performance. That's not possible if the element is not in a document.

是的,但是该方法利用文档中 id 唯一的事实来优化性能。如果元素不在文档中,这是不可能的。

"It could increase the performance, if the document has lots of elements and you know that the element you are searching is a descendant of an element that you already have in a variable"

“如果文档有很多元素,并且您知道要搜索的元素是变量中已有元素的后代,则它可以提高性能”

No, because it's already using a method that doesn't have to look through all the elements to find the id. Looping through elements would just make it slower.

不,因为它已经在使用一种无​​需查看所有元素即可找到 id 的方法。循环遍历元素只会让它变慢。