6

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

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

    <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[]会好很多);

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

4

3 回答 3

5

主要原因是效率。

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

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

于 2013-07-07T00:07:47.723 回答
4

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

于 2013-07-07T00:24:45.033 回答
0

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

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

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

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

于 2013-07-06T23:56:01.120 回答