34

我想找到具有 id 的子 div 的索引'whereami'

<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节点,所以需要重新计算。

4

9 回答 9

40

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

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

跳转到 jsperf 结果

事实证明,jQuery 比您的实现(在 Chrome/Mac 上)慢了大约 50%,而我的实现可以说比它慢 1%。

编辑

不能完全放过这个,所以我添加了另外两种方法:

使用 Array.indexOf

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

对我早期的实验代码的改进,如HBP 的回答中所见,它DOMNodeList被视为一个数组,它用于Array.indexOf()确定.parentNode.children其中所有元素的位置。我的第一次尝试是使用.parentNode.childNodes,但由于文本节点而给出了不正确的结果。

使用 previousElementSibling

user1689607 的回答启发,最近的浏览器除了被调用之外还有另一个属性.previousSiblingpreviousElementSibling,它将两个原始语句合二为一。IE <= 8 没有此属性,但.previousSibling已经具有此属性,因此可以进行特征检测

(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 <= 8 浏览器不支持使用Array.indexOf(),仿真速度不够快;但是,它确实提高了 20% 的性能。

使用特征检测并.previousElementSibling产生 7 倍的改进(在 Chrome 上),我还没有在 IE8 上对其进行测试。

于 2012-12-01T08:05:55.660 回答
5

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

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

[仅在 Chrome 浏览器上测试]

于 2012-12-01T07:39:51.753 回答
4

我在jsPerf 测试中添加了两个测试。两者都使用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我认为根据 a 的存在将它分成两个不同的循环只会有帮助。

也许我会稍微补充一下。

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

于 2012-12-01T14:51:46.897 回答
2

比 Jack 的解决方案略有改进,提高了 3%。确实有点奇怪。

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

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

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

所以 xor 3 with nodeType, 将给出2and 0

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

于 2012-12-01T09:04:12.977 回答
1

由于您使用的是 jQuery。索引应该可以解决问题

jQuery('#whereami').index()

但是以后你打算如何使用索引呢?

于 2012-12-01T06:56:13.667 回答
1

试试这个:

function findRow(node) {
    var i = 1;
    while ((node = node.previousSibling) != null) {
        if (node.nodeType === 1) i++;
    }
    return i; //Returns 3
}
于 2012-12-01T07:41:51.253 回答
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

于 2012-12-01T15:05:07.093 回答
1

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

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

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

(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;
Body parentIndex is <b id="output"></b><br />
documentElements parentIndex is <b id="output2"></b>

限制

  • 此解决方案的实现在 IE8 及更低版本中不起作用。

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

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

二进制搜索

(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);
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

向后(`lastIndexOf`)线性搜索

(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);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

前向 (`indexOf`) 线性搜索

(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);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

PreviousElementSibling 计数器搜索

计算 PreviousElementSiblings 的数量以获取 parentIndex。

(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);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden;white-space:pre></div>

没有搜索

如果浏览器优化了搜索,测试结果将是基准测试。

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);
});
<output id=output> </output><br />
<div id=test style=visibility:hidden></div>

结论

但是,在 Chrome 中查看结果后,结果与预期相反。更笨的前向线性搜索是惊人的 187 毫秒,3850%,比二分搜索快。显然,Chrome 不知何故神奇地超越console.assert并优化了它,或者(更乐观地)Chrome 在内部使用 DOM 的数字索引系统,并且这个内部索引系统通过在对象Array.prototype.indexOf上使用时应用的优化暴露出来。HTMLCollection

于 2017-07-02T22:13:37.407 回答
1

2020 年 - 使用表格的解决方案 - Clean Vanilla JS 方法

归功于: 阿兰克鲁兹

资料来源: 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>

干杯! 斯特凡诺

于 2020-07-21T18:23:16.133 回答