为什么没有`.getElementById`在节点上工作?

时间:2013-07-06 23:44:57

标签: javascript dom

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

  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. 如果文档包含大量元素并且您知道要搜索的元素是变量中已有的元素的后代,则可以提高性能

  3. p>

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

    <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[].children[]会更好一些);

    然后,为什么.getElementById不在节点上工作?我没有看到任何缺点,只有优点

3 个答案:

答案 0 :(得分:5)

主要原因是效率。

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

另一个需要注意的重要事项是(从Javascript的角度来看,至少)每个元素都归文档(或文档片段)所有。因此,&#34;的论点,但我可以有重复的ID,只要其中只有一个附加到文档&#34;并没有真正叠加 - 当你考虑到这一点时,

答案 1 :(得分:3)

关于您在第一个示例/要求中描述的问题。由于getElementById仅存在于document节点上,因为它使用的缓存仅由属于document的节点树提供。搜索未附加到document的节点树有三种选择。由于没有利用document缓存,所有都遭受0(log n),很少或根本没有办法(你试过iFrame)。

一个:递归节点漫游器。

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

缺点是它始终为0(log n) - 如果在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>');

jsfiddle

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

优点是它需要更少的代码

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

的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

三是使用TreeWalker

缺点是它需要IE9 +,比以前的方法更难理解(经常被遗忘),并且需要的代码多于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

现在,关于这些方法的效果,请参阅此jsperf

注意:如果节点是document的一部分,则三种方法的性能会发生显着变化!

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

更新:如果异步对您的要求不是问题,那么您可以使用iframe这样做。

优点是您现在可以使用getElementById

缺点是创建和销毁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

答案 2 :(得分:0)

  

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

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

  

“如果文档有很多,它可以提高性能   元素,你知道你正在搜索的元素是   你已经在变量“

中拥有的元素的后代

不,因为它已经在使用一种方法,不需要查看所有元素来查找id。循环使用元素会使速度变慢。