Fastest way to find the index of a child node in parent

By co-opting Array indexOf you could use :

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

[Tested on Chrome browser only]


A little improvement over Jack's solution, 3% improvement. Little weird indeed.

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

As there are only two possible nodeTypes in this case (and in most cases):

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

So xor 3 with nodeType, will give 2 and 0.

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


Out of curiosity I ran your code against both jQuery's .index() and my below code:

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

Jump to jsperf results

It turns out that jQuery is roughly 50% slower than your implementation (on Chrome/Mac) and mine arguably topped it by 1%.

Edit

Couldn't quite let this one go, so I've added two more approaches:

Using Array.indexOf

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

Improvement on my earlier experimental code, as seen in HBP's answer, the DOMNodeList is treated like an array and it uses Array.indexOf() to determine the position within its .parentNode.children which are all elements. My first attempt was using .parentNode.childNodes but that gives incorrect results due to text nodes.

Using previousElementSibling

Inspired by user1689607's answer, recent browsers have another property besides .previousSibling called .previousElementSibling, which does both original statements in one. IE <= 8 doesn't have this property, but .previousSibling already acts as such, therefore a feature detection would work.

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

Conclusion

Using Array.indexOf() is not supported on IE <= 8 browsers, and the emulation is simply not fast enough; however, it does give 20% performance improvement.

Using feature detection and .previousElementSibling yields a 7x improvement (on Chrome), I have yet to test it on IE8.


I added two tests to the jsPerf test. Both use previousElementSibling, but the second includes compatibility code for IE8 and lower.

Both of them perform extremely well in modern browsers (which is most browsers in use today), but will take a small hit in older browsers.


Here's the first one that doesn't include the compatibility fix. It'll work in IE9 and higher, as well as pretty much all of Firefox, Chrome and Safari.

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

Here's the version with the compatibility fix.

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

Because it automatically grabs element siblings, there's no test needed for nodeType, and the loop is shorter overall. This explains the large performance increase.


I also added one last version that loops the .children, and compares the node to each one.

This isn't quite as fast as the previousElementSibling versions, but is still faster than the others (at least in 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;
}


Going back to the previousElementSibling version, here's a tweak that may bump up the performance just a bit.

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

I haven't tested it in the jsPerf, but breaking it out into two different loops based on the presence of a previouselementSibling would only help I would think.

Maybe I'll add it in a bit.

I went ahead and added it to the test linked at the top of this answer. It does help a little bit, so I think it's probably worth doing.