查找父节点中子节点索引的最快方法

时间:2012-12-01 06:05:28

标签: javascript html dom

我想找到id为'whereami'的子div的索引。

<div id="parent">
   <div></div>
   <div></div>
   <div id="whereami"></div>
   <div></div>
</div>

目前我正在使用此功能查找孩子的索引。

function findRow(node){
    var i=1;
    while(node.previousSibling){
        node = node.previousSibling;
        if(node.nodeType === 1){
            i++;
        }
    }
    return i; //Returns 3
}

var node = document.getElementById('whereami'); //div node to find
var index = findRow(node);

小提琴:http://jsfiddle.net/grantk/F7JpH/2/

问题
当存在数千个div节点时,while循环必须遍历每个div以对它们进行计数。这可能需要一段时间。

有没有更快的方法来解决这个问题?

*请注意,id将更改为不同的div节点,因此需要能够重新计算。

9 个答案:

答案 0 :(得分:37)

出于好奇,我针对jQuery的.index()和我的下面代码运行了代码:

function findRow3(node)
{
    var i = 1;
    while (node = node.previousSibling) {
        if (node.nodeType === 1) { ++i }
    }
    return i;
}

Jump to jsperf results

事实证明,jQuery比你的实现(在Chrome / Mac上)大约慢50%,并且可以说它高出1%。

修改

不能完全放弃这个,所以我又增加了两个方法:

使用Array.indexOf

[].indexOf.call(node.parentNode.children, node);

我早期的实验代码的改进,如HBP's answer所示,DOMNodeList被视为一个数组,它使用Array.indexOf()来确定其.parentNode.children中的位置都是元素。我的第一次尝试是使用.parentNode.childNodes但由于文本节点而导致结果不正确。

使用previousElementSibling

user1689607's answer的启发,最近的浏览器除了.previousSibling之外还有另一个名为previousElementSibling的属性,它将两个原始语句组合在一起。 IE&lt; = 8没有此属性,但.previousSibling已经这样做,因此feature detection可以正常工作。

(function() {
    // feature detection
    // use previousElementSibling where available, IE <=8 can safely use previousSibling
    var prop = document.body.previousElementSibling ? 'previousElementSibling' : 'previousSibling';

    getElementIndex = function(node) {
        var i = 1;
        while (node = node[prop]) { ++i }
        return i;
    }

结论

IE&lt; = 8浏览器不支持使用Array.indexOf(),仿真速度不够快;但是,它确实提高了20%的性能。

使用功能检测和.previousElementSibling可以提高7倍(在Chrome上),我还没有在IE8上进行测试。

答案 1 :(得分:5)

通过选择Array indexOf,您可以使用:

  var wmi = document.getElementById ('whereami');
  index = [].indexOf.call (wmi.parentNode.children, wmi);

[仅在Chrome浏览器上测试]

答案 2 :(得分:3)

我在jsPerf test添加了两个测试。两者都使用previousElementSibling,但第二个包含IE8及更低版本的兼容性代码。

它们在现代浏览器(目前使用的大多数浏览器)中表现都非常出色,但在旧浏览器中只会受到轻微打击。


这是第一个不包含兼容性修补程序的内容。它适用于IE9及更高版本,以及几乎所有的Firefox,Chrome和Safari。

function findRow6(node) {
    var i = 1;
    while (node = node.previousElementSibling)
        ++i;
    return i;
}

以下是具有兼容性修补程序的版本。

function findRow7(node) {
    var i = 1,
        prev;
    while (true)
        if (prev = node.previousElementSibling) {
            node = prev;
            ++i;
        } else if (node = node.previousSibling) {
            if (node.nodeType === 1) {
                ++i;
            }
        } else break;
    return i;
}

因为它会自动抓取元素兄弟,所以nodeType不需要测试,循环总体上更短。这解释了性能的大幅提升。


我还添加了一个循环.children的最后一个版本,并将node与每个版本进行比较。

这不如previousElementSibling版本快,但仍然比其他(至少在Firefox中)更快。

function findRow8(node) {
    var children = node.parentNode.children,
        i = 0,
        len = children.length;
    for( ; i < len && children[i] !== node; i++)
        ; // <-- empty statement

    return i === len ? -1 : i;
}


回到previousElementSibling版本,这是一个可能会稍微提高性能的调整。

function findRow9(node) {
    var i = 1,
        prev = node.previousElementSibling;

    if (prev) {
        do ++i;
        while (prev = prev.previousElementSibling);
    } else {
        while (node = node.previousSibling) {
            if (node.nodeType === 1) {
                ++i;
            }
        }
    }
    return i;
}

我没有在jsPerf中对它进行过测试,但根据previouselementSibling的存在将其分解为两个不同的循环只会让我觉得有用。

也许我会稍微添加一下。

我继续将其添加到此答案顶部链接的测试中。它确实有点帮助,所以我认为它可能值得做。

答案 3 :(得分:2)

杰克解决方案略有改进,提升3%。确实有点奇怪。

function findRow5(node)
{
    var i = 2;
    while (node = node.previousSibling)
        i += node.nodeType ^ 3;
    return i >> 1;
}

因为在这种情况下(在大多数情况下)只有两个可能的nodeType

Node.ELEMENT_NODE == 1
Node.TEXT_NODE == 3

因此,nodeType的xor 3将提供20

http://jsperf.com/sibling-index/4

答案 4 :(得分:1)

因为你正在使用jQuery。索引应该做的伎俩

jQuery('#whereami').index()

但是你将如何在以后使用该索引?

答案 5 :(得分:1)

试试这个:

function findRow(node) {
    var i = 1;
    while ((node = node.previousSibling) != null) {
        if (node.nodeType === 1) i++;
    }
    return i; //Returns 3
}

答案 6 :(得分:1)

一般来说,除非代码在循环中运行,否则性能上的微小差异可以忽略不计。必须一次而不是每次运行代码都会快得多。

做一次这样的事情:

var par = document.getElementById('parent');
var childs = par.getElementsByTagName('*');
for (var i=0, len = childs.length;i < len;i++){
  childs[i].index = i;
}

随后找到索引就像:

document.getElementById('findme').index

听起来好像你正在做的事情可以通过在DOM和javascript之间建立更清晰的关系而受益。考虑学习Backbone.js,这是一个小型的JavaScript MVC库,它使Web应用程序更容易控制。

编辑:我删除了我使用的jQuery。我通常会避免使用它,但是在SO上有相当偏好,所以我认为它最终会被使用。在这里你可以看到明显的区别:http://jsperf.com/sibling-index/8

答案 7 :(得分:1)

我假设给定一个元素,其中所有子元素按顺序排列在文档上,最快的方法应该是进行二元搜索,比较元素的文档位置。但是,正如结论中所介绍的那样,假设被拒绝了。您拥有的元素越多,性能潜力就越大。例如,如果您有256个元素,那么(最佳)您只需要检查其中的16个元素!对于65536,只有256!性能增长到2的力量!查看更多数字/统计数据。访问Wikipedia

(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndex', {
      get: function() {
        var searchParent = this.parentElement;
        if (!searchParent) return -1;
        var searchArray = searchParent.children,
            thisOffset = this.offsetTop,
            stop = searchArray.length,
            p = 0,
            delta = 0;

        while (searchArray[p] !== this) {
            if (searchArray[p] > this)
                stop = p + 1, p -= delta;
            delta = (stop - p) >>> 1;
            p += delta;
        }

        return p;
      }
    });
})(window.Element || Node);

然后,您使用它的方式是获取&#39; parentIndex&#39;任何元素的属性。例如,请查看以下演示。

&#13;
&#13;
(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndex', {
      get: function() {
        var searchParent = this.parentNode;
        if (searchParent === null) return -1;
        var childElements = searchParent.children,
            lo = -1, mi, hi = childElements.length;
        while (1 + lo !== hi) {
            mi = (hi + lo) >> 1;
            if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) {
                hi = mi;
                continue;
            }
            lo = mi;
        }
        return childElements[hi] === this ? hi : -1;
      }
    });
})(window.Element || Node);

output.textContent = document.body.parentIndex;
output2.textContent = document.documentElement.parentIndex;
&#13;
Body parentIndex is <b id="output"></b><br />
documentElements parentIndex is <b id="output2"></b>
&#13;
&#13;
&#13;

<强>限制

  • 此解决方案的实施在IE8及以下版本中无效。

二进制VS线性搜索20万个元素(可能会崩溃某些移动浏览器,请注意!):

  • 在此测试中,我们将看到线性搜索找到中间元素VS二进制搜索所需的时间。为什么中间元素?因为它位于所有其他位置的平均位置,所以它最好代表所有可能的位置。

二进制搜索

&#13;
&#13;
(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndexBinarySearch', {
      get: function() {
        var searchParent = this.parentNode;
        if (searchParent === null) return -1;
        var childElements = searchParent.children,
            lo = -1, mi, hi = childElements.length;
        while (1 + lo !== hi) {
            mi = (hi + lo) >> 1;
            if (!(this.compareDocumentPosition(childElements[mi]) & 0x2)) {
                hi = mi;
                continue;
            }
            lo = mi;
        }
        return childElements[hi] === this ? hi : -1;
      }
    });
})(window.Element || Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99.9e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=200 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99.9e+3+i+Math.random())).parentIndexBinarySearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the binary search ' + ((end-start)*10).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
}, 125);
&#13;
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
&#13;
&#13;
&#13;

向后(`lastIndexOf`)线性搜索

&#13;
&#13;
(function(t){"use strict";var e=Array.prototype.lastIndexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the backwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
&#13;
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
&#13;
&#13;
&#13;

前锋(`indexOf`)线性搜索

&#13;
&#13;
(function(t){"use strict";var e=Array.prototype.indexOf;Object.defineProperty(t.prototype,"parentIndexLinearSearch",{get:function(){return e.call(t,this)}})})(window.Element||Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99e+3+i+Math.random())).parentIndexLinearSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the forwards linear search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
&#13;
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
&#13;
&#13;
&#13;

PreviousElementSibling Counter Search

计算PreviousElementSiblings的数量以获取parentIndex。

&#13;
&#13;
(function(constructor){
   'use strict';
    Object.defineProperty(constructor.prototype, 'parentIndexSiblingSearch', {
      get: function() {
        var i = 0, cur = this;
        do {
            cur = cur.previousElementSibling;
            ++i;
        } while (cur !== null)
        return i; //Returns 3
      }
    });
})(window.Element || Node);
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var child=test.children.item(99.95e+3);
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=100 + end; i-- !== end; )
    console.assert( test.children.item(
        Math.round(99.95e+3+i+Math.random())).parentIndexSiblingSearch );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the PreviousElementSibling search ' + ((end-start)*20).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
&#13;
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>
&#13;
&#13;
&#13;

无搜索

如果浏览器优化了搜索,则用于对测试结果进行基准测试。

&#13;
&#13;
test.innerHTML = '<div> </div> '.repeat(200e+3);
// give it some time to think:
requestAnimationFrame(function(){
  var start=performance.now(), end=Math.round(Math.random());
  for (var i=2000 + end; i-- !== end; )
    console.assert( true );
  var end=performance.now();
  setTimeout(function(){
    output.textContent = 'It took the no search ' + (end-start).toFixed(2) + 'ms to find the 999 thousandth to 101 thousandth children in an element with 200 thousand children.';
    test.remove();
    test = null; // free up reference
  }, 125);
});
&#13;
<output id=output> </output><br />
<div id=test style=visibility:hidden></div>
&#13;
&#13;
&#13;

The Conculsion

但是,在Chrome中查看结果后,结果与预期相反。数字转发线性搜索是一个令人惊讶的187毫秒,3850%,比二进制搜索更快。很明显,Chrome以某种方式神奇地超越了console.assert并对其进行了优化,或者(更乐观地)Chrome内部使用了数字索引系统来实现DOM,而这个内部索引系统通过应用于Array.prototype.indexOf的优化来公开。用于HTMLCollection对象。

答案 8 :(得分:1)

2020年-如果您使用的是表格解决方案-清洁香草JS方法

信用额度: Alain Cruz

来源: https://stackoverflow.com/a/58845058/3626361(如果有帮助的话,请给他投票)

问题:如何通过单击返回表单元格的行和列索引?

const cells = document.querySelectorAll('td');
cells.forEach(cell => {
  cell.addEventListener('click', () =>
    console.log("Row index: " + cell.closest('tr').rowIndex + " | Column index: " + cell.cellIndex));
});
<table>
  <tr>
    <td>0:0</td>
    <td>0:1</td>
    <td>0:2</td>
    <td>0:3</td>
  </tr>
  <tr>
    <td>1:0</td>
    <td>1:1</td>
    <td>1:2</td>
    <td>1:3</td>
  </tr>
  <tr>
    <td>2:0</td>
    <td>2:1</td>
    <td>2:2</td>
    <td>2:3</td>
  </tr>
</table>

干杯!斯特凡诺