Holen Sie sich den untergeordneten Knotenindex


124

Gibt es in direktem Javascript (dh ohne Erweiterungen wie jQuery usw.) eine Möglichkeit, den Index eines untergeordneten Knotens innerhalb seines übergeordneten Knotens zu bestimmen, ohne alle untergeordneten Knoten zu durchlaufen und zu vergleichen?

Z.B,

var child = document.getElementById('my_element');
var parent = child.parentNode;
var childNodes = parent.childNodes;
var count = childNodes.length;
var child_index;
for (var i = 0; i < count; ++i) {
  if (child === childNodes[i]) {
    child_index = i;
    break;
  }
}

Gibt es eine bessere Möglichkeit, den Index des Kindes zu bestimmen?


2
Entschuldigung, bin ich ein kompletter Trottel? Hier gibt es viele scheinbar gelernte Antworten, aber um alle untergeordneten Knoten zu erhalten, müssen Sie das nicht tun parent.childNodes, anstatt parent.children? Letzteres listet nur die Elements, mit Ausnahme bestimmter TextKnoten, auf ... Einige der Antworten hier, z. B. using previousSibling, basieren auf der Verwendung aller untergeordneten Knoten, während andere sich nur mit Kindern beschäftigen, die Element... (!)
Mike Nagetier

@mikerodent Ich erinnere mich nicht, was mein Zweck war, als ich diese Frage zum ersten Mal stellte, aber das ist ein wichtiges Detail, das mir nicht bekannt war. Es sei denn, Sie sind vorsichtig, .childNodessollte auf jeden Fall anstelle von verwendet werden .children. Die beiden am häufigsten veröffentlichten Antworten führen zu unterschiedlichen Ergebnissen, wie Sie bereits betont haben.
Alle Arbeiter sind wesentlich

Wenn Sie Tausende von Suchvorgängen über 1000 Knoten durchführen möchten, fügen Sie dem Knoten Informationen hinzu (z. B. über child.dataset). Das Ziel wäre, einen O (n) - oder O (n ^ 2) -Algorithmus in einen O (1) -Algorithmus umzuwandeln. Der Nachteil ist, dass beim regelmäßigen Hinzufügen und Entfernen von Knoten auch die zugehörigen Positionsinformationen, die an die Knoten angehängt sind, aktualisiert werden müssen, was möglicherweise zu keinen Leistungssteigerungen führt. Die gelegentliche Iteration ist keine große Sache (z. B. Klick-Handler), aber wiederholte Iterationen sind problematisch (z. B. Mausbewegung).
CubicleSoft

Antworten:


122

Sie können die previousSiblingEigenschaft verwenden, um die Geschwister zu durchlaufen, bis Sie zurückkehren nullund zählen, wie viele Geschwister Sie angetroffen haben:

var i = 0;
while( (child = child.previousSibling) != null ) 
  i++;
//at the end i will contain the index.

Bitte beachten Sie, dass es in Sprachen wie Java eine getPreviousSibling()Funktion gibt, in JS ist dies jedoch eine Eigenschaft geworden - previousSibling.


2
Ja. Sie haben jedoch ein getPreviousSibling () im Text hinterlassen.
Tim Down

7
Dieser Ansatz erfordert die gleiche Anzahl von Iterationen, um den untergeordneten Index zu bestimmen, daher kann ich nicht sehen, wie viel schneller er sein würde.
Michael

31
Einzeilige Version:for (var i=0; (node=node.previousSibling); i++);
Scott Miles

2
@sfarbota Javascript kennt das Block-Scoping nicht und ist daher izugänglich.
A1rPun

3
@nepdev Das wäre wegen der Unterschiede zwischen .previousSiblingund .previousElementSibling. Ersteres trifft Textknoten, letzteres nicht.
Abluejelly

132

Ich habe es gern benutzt indexOf. Weil eingeschaltet indexOfist Array.prototypeund ein parent.childrenist NodeList, muss man es verwenden. call();Es ist irgendwie hässlich, aber es ist ein Einzeiler und verwendet Funktionen, mit denen jeder Javascript-Entwickler sowieso vertraut sein sollte.

var child = document.getElementById('my_element');
var parent = child.parentNode;
// The equivalent of parent.children.indexOf(child)
var index = Array.prototype.indexOf.call(parent.children, child);

7
var index = [] .indexOf.call (child.parentNode.children, child);
Cuixiping

23
Fwiw erstellt mit using []jedes Mal eine Array-Instanz, wenn Sie diesen Code ausführen, was für Speicher und GC weniger effizient ist als für using Array.prototype.
Scott Miles

@ScottMiles Darf ich fragen, was Sie noch ein bisschen gesagt haben? Wird der []Speicher nicht als Müllwert sauber?
MrReiha

14
Um [].indexOfdie Engine auszuwerten , muss eine Array-Instanz erstellt werden, um auf die indexOfImplementierung des Prototyps zugreifen zu können . Die Instanz selbst wird nicht verwendet (es wird GC ausgeführt, es ist kein Leck, es werden nur Zyklen verschwendet). Array.prototype.indexOfgreift direkt auf diese Implementierung zu, ohne eine anonyme Instanz zuzuweisen. Der Unterschied wird unter fast allen Umständen vernachlässigbar sein, so dass es sich offen gesagt möglicherweise nicht lohnt, sich darum zu kümmern.
Scott Miles

3
Vorsicht vor Fehlern im IE! Internet Explorer 6, 7 und 8 unterstützten dies, enthielten jedoch fälschlicherweise Kommentarknoten. Quelle " developer.mozilla.org/en-US/docs/Web/API/ParentNode/…
Luckylooke

100

ES6:

Array.from(element.parentNode.children).indexOf(element)

Erklärung:

  • element.parentNode.children→ Gibt die Brüder von elementeinschließlich dieses Elements zurück.

  • Array.from→ Wirkt den Konstruktor childrenauf ein ArrayObjekt

  • indexOf→ Sie können sich bewerben, indexOfweil Sie jetzt ein ArrayObjekt haben.


2
Mit Abstand eleganteste Lösung :)
Amit Saxena

1
Aber nur in Chrome 45 und Firefox 32 verfügbar und im Internet Explorer überhaupt nicht verfügbar.
Peter

Internet Explorer noch am Leben? Just Jock .. Ok, Sie benötigen also eine Polyfüllung , um Array.fromArbeiten im Internet Explorer
ausführen zu können

9
Laut MDN ist das Aufrufen von Array.from () zum creates a new Array instance from an array-like or iterable object. Erstellen einer neuen Array-Instanz, um nur einen Index zu finden, möglicherweise nicht speicher- oder GC-effizient, je nachdem, wie häufig die Operation ausgeführt wird. In diesem Fall wäre die Iteration, wie in der akzeptierten Antwort erläutert, häufiger Ideal.
TheDarkIn1978

1
@ TheDarkIn1978 Ich bin mir bewusst, dass es einen Kompromiss zwischen Code-Eleganz und App-Leistung gibt 👍🏻
Abdennour TOUMI

38

ES - kürzer

[...element.parentNode.children].indexOf(element);

Der Spread-Operator ist eine Abkürzung dafür


Das ist ein interessanter Operator.
Alle Arbeiter sind essentiell

Was ist der Unterschied zwischen e.parentElement.childNodesund e.parentNode.children?
Mesqueeb

4
childNodesenthält auch
Textknoten

Mit Typescript erhalten Sie Type 'NodeListOf<ChildNode>' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)
ZenVentzi

9

Hinzufügen eines (aus Sicherheitsgründen vorangestellten) Elements.getParentIndex ():

Element.prototype.PREFIXgetParentIndex = function() {
  return Array.prototype.indexOf.call(this.parentNode.children, this);
}

1
Es gibt einen Grund für die Schmerzhaftigkeit der Webentwicklung: Präfix-Jumpy-Entwickler. Warum nicht einfach tun if (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }? Wenn dies in Zukunft jemals in den Standard implementiert wird, wird es wahrscheinlich als Getter-ähnliche implementiert element.parentIndex. Also, ich würde sagen, der beste Ansatz wäreif(!Element.prototype.getParentIndex) Element.prototype.getParentIndex=Element.prototype.parentIndex?function() {return this.parentIndex}:function() {return Array.prototype.indexOf.call(this.parentNode.children, this)}
Jack Giffin

3
Weil die Zukunft getParentIndex()möglicherweise eine andere Signatur hat als Ihre Implementierung.
Mikemaccana

7

Ich gehe davon aus, dass bei einem Element, bei dem alle untergeordneten Elemente nacheinander im Dokument angeordnet sind, der schnellste Weg darin bestehen sollte, eine binäre Suche durchzuführen und die Dokumentpositionen der Elemente zu vergleichen. Wie in der Schlussfolgerung eingeführt, wird die Hypothese jedoch zurückgewiesen. Je mehr Elemente Sie haben, desto größer ist das Leistungspotenzial. Wenn Sie beispielsweise 256 Elemente hätten, müssten Sie (optimalerweise) nur 16 davon überprüfen! Für 65536 nur 256! Die Leistung wächst auf 2! Weitere Zahlen / Statistiken anzeigen. Besuchen Sie 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);

Die Art und Weise, wie Sie es verwenden, besteht darin, die Eigenschaft 'parentIndex' eines beliebigen Elements abzurufen. Schauen Sie sich zum Beispiel die folgende Demo an.

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

Einschränkungen

  • Diese Implementierung der Lösung funktioniert in IE8 und darunter nicht.

Binäre VS Lineare Suche Auf 200.000 Elementen (könnte einige mobile Browser zum Absturz bringen, ACHTUNG!):

  • In diesem Test werden wir sehen, wie lange es dauert, bis eine lineare Suche das mittlere Element im Vergleich zu einer binären Suche findet. Warum das mittlere Element? Da es sich am durchschnittlichen Standort aller anderen Standorte befindet, werden alle möglichen Standorte am besten dargestellt.

Binäre Suche

Rückwärts (`lastIndexOf`) Lineare Suche

Vorwärts (`indexOf`) Lineare Suche

PreviousElementSibling Counter Search

Zählt die Anzahl der PreviousElementS Geschwister, um den parentIndex abzurufen.

Keine Suche

Für das Benchmarking wäre das Ergebnis des Tests, wenn der Browser die Suche optimiert hätte.

Der Zusammenstoß

Nach dem Anzeigen der Ergebnisse in Chrome sind die Ergebnisse jedoch das Gegenteil von den erwarteten Ergebnissen. Die dümmer vorwärts gerichtete lineare Suche war überraschend 187 ms, 3850%, schneller als die binäre Suche. Offensichtlich hat Chrome das auf magische Weise überlistet console.assertund optimiert, oder (optimistischer) Chrome verwendet intern ein numerisches Indizierungssystem für das DOM, und dieses interne Indizierungssystem wird durch die Optimierungen verfügbar gemacht, die Array.prototype.indexOfbei der Verwendung auf ein HTMLCollectionObjekt angewendet werden.


Effizient, aber unpraktisch.
applemonkey496

3

Verwenden Sie den binären Suchalgorithmus , um die Leistung zu verbessern, wenn der Knoten Geschwister mit großer Anzahl hat.

function getChildrenIndex(ele){
    //IE use Element.sourceIndex
    if(ele.sourceIndex){
        var eles = ele.parentNode.children;
        var low = 0, high = eles.length-1, mid = 0;
        var esi = ele.sourceIndex, nsi;
        //use binary search algorithm
        while (low <= high) {
            mid = (low + high) >> 1;
            nsi = eles[mid].sourceIndex;
            if (nsi > esi) {
                high = mid - 1;
            } else if (nsi < esi) {
                low = mid + 1;
            } else {
                return mid;
            }
        }
    }
    //other browsers
    var i=0;
    while(ele = ele.previousElementSibling){
        i++;
    }
    return i;
}

Funktioniert nicht Ich muss darauf hinweisen, dass die IE-Version und die Version "anderer Browser" unterschiedliche Ergebnisse berechnen. Die Technik "Andere Browser" funktioniert wie erwartet und erhält die n-te Position unter dem übergeordneten Knoten. Die IE-Technik "Ruft die Ordnungsposition des Objekts in der Quellreihenfolge ab , wenn das Objekt in der gesamten Sammlung des Dokuments angezeigt wird " ( msdn.microsoft) .com / en-gb / library / ie / ms534635 (v = vs.85) .aspx ). ZB habe ich 126 mit "IE" -Technik und dann 4 mit der anderen.
Christopher Bull

1

Ich hatte ein Problem mit Textknoten und es wurde ein falscher Index angezeigt. Hier ist die Version, um das Problem zu beheben.

function getChildNodeIndex(elem)
{   
    let position = 0;
    while ((elem = elem.previousSibling) != null)
    {
        if(elem.nodeType != Node.TEXT_NODE)
            position++;
    }

    return position;
}

0
Object.defineProperties(Element.prototype,{
group : {
    value: function (str, context) {
        // str is valid css selector like :not([attr_name]) or .class_name
        var t = "to_select_siblings___";
        var parent = context ? context : this.parentNode;
        parent.setAttribute(t, '');
        var rez = document.querySelectorAll("[" + t + "] " + (context ? '' : ">") + this.nodeName + (str || "")).toArray();
        parent.removeAttribute(t);            
        return rez;  
    }
},
siblings: {
    value: function (str, context) {
        var rez=this.group(str,context);
        rez.splice(rez.indexOf(this), 1);
        return rez; 
    }
},
nth: {  
    value: function(str,context){
       return this.group(str,context).indexOf(this);
    }
}
}

Ex

/* html */
<ul id="the_ul">   <li></li> ....<li><li>....<li></li>   </ul>

 /*js*/
 the_ul.addEventListener("click",
    function(ev){
       var foo=ev.target;
       foo.setAttribute("active",true);
       foo.siblings().map(function(elm){elm.removeAttribute("active")});
       alert("a click on li" + foo.nth());
     });

1
Können Sie erklären, warum Sie von verlängern Element.prototype? Die Funktionen sehen nützlich aus, aber ich weiß nicht, was diese Funktionen tun (auch wenn die Namen offensichtlich sind).
A1rPun

@ verlängern Element.prototype der Grund ist Ähnlichkeit ... 4 ex elemen.children, element.parentNode etc ... also genauso wie Sie element.s Geschwister ansprechen .... die Gruppenmethode ist ein wenig kompliziert, weil ich erweitern möchte ein wenig die Geschwister Annäherung an elelemts gleichermaßen durch den gleichen Knotentyp und mit den gleichen Attributen, auch wenn sie nicht den gleichen Vorfahren haben
bortunac

Ich weiß, was Prototypenerweiterung ist, aber ich möchte wissen, wie Ihr Code verwendet wird. el.group.value()??. Mein erster Kommentar dient dazu, die Qualität Ihrer Antwort zu verbessern.
A1rPun

Die Gruppen- und Geschwistermethoden geben Array mit gegründeten dom-Elementen zurück. .... danke für Ihren Kommentar und für den Grund des Kommentars
bortunac

Sehr elegant, aber auch sehr langsam.
Jack Giffin


-1
<body>
    <section>
        <section onclick="childIndex(this)">child a</section>
        <section onclick="childIndex(this)">child b</section>
        <section onclick="childIndex(this)">child c</section>
    </section>
    <script>
        function childIndex(e){
            let i = 0;
            while (e.parentNode.children[i] != e) i++;
            alert('child index '+i);
        }
    </script>
</body>

Sie brauchen jQuery hier nicht.
Vitaly Zdanevich
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.