Obține indexul nodului copil (Programare, Javascript, Dom)

Toți lucrătorii sunt esențiali a intrebat.
a intrebat.

În javascript direct (adică, fără extensii precum jQuery etc.), există o modalitate de a determina indexul unui nod copil în interiorul nodului său părinte fără a itera și compara toate nodurile copii?

De ex,

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

Există o modalitate mai bună de a determina indexul copilului?

Comentarii

  • Scuzați-mă, sunt un prostănac complet? Există o mulțime de răspunsuri aparent învățate aici, dar pentru a obține toate nodurile copii nu trebuie să faceți următoarele operații parent.childNodes, , în loc de parent.children?. Acesta din urmă listează doar Elements, excluzând în special Text noduri… Unele dintre răspunsurile de aici, de exemplu, folosind previousSibling, , se bazează pe utilizarea tuturor nodurilor copil, în timp ce altele sunt deranjate doar de copiii care sunt Elements… (!) –  > Por mike rodent.
  • @mikerodent Nu-mi amintesc care era scopul meu când am pus inițial această întrebare, dar acesta este un detaliu cheie de care nu eram conștient. Dacă nu ești atent, .childNodes ar trebui să fie folosit cu siguranță în loc de .children. Primele 2 răspunsuri postate vor da rezultate diferite, așa cum ați subliniat. –  > Por Toți lucrătorii sunt esențiali.
  • Dacă intenționați să efectuați mii de căutări în peste 1000+ noduri, atunci atașați informații la nod (de exemplu, prin child.dataset). Scopul ar fi de a transforma un algoritm O(n) sau O(n^2) într-un algoritm O(1). Dezavantajul este că, în cazul în care nodurile sunt adăugate și eliminate în mod regulat, informațiile de poziție asociate atașate nodurilor vor trebui să fie și ele actualizate, ceea ce ar putea să nu ducă la câștiguri de performanță. Iterația ocazională nu este o mare problemă (de exemplu, gestionarul de clic), dar iterația repetată este problematică (de exemplu, mutarea mouse-ului). –  > Por CubicleSoft.
11 răspunsuri
Liv

puteți folosi previousSibling pentru a itera înapoi printre frați până când ajungeți înapoi la null și să numeri câți frați ai întâlnit:

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

Rețineți că în limbaje precum Java, există un getPreviousSibling() funcție, însă în JS aceasta a devenit o proprietate — previousSibling.

Comentarii

  • Da. Ai lăsat totuși un getPreviousSibling() în text. –  > Por Tim Down.
  • această abordare necesită același număr de iterații pentru a determina indexul copilului, așa că nu văd cum ar putea fi mult mai rapidă. –  > Por Michael.
  • 34

  • Versiunea cu o singură linie: for (var i=0; (node=node.previousSibling); i++); –  > Por Scott Miles.
  • @sfarbota Javascript nu cunoaște block scoping, așa că i va fi accesibil. –  > Por A1rPun.
  • @nepdev Asta ar fi din cauza diferențelor dintre .previousSibling și .previousElementSibling. Primul lovește nodurile de text, al doilea nu. –  > Por abluejelly.
KhalilRavanna

Mi-a plăcut să folosesc indexOf pentru acest lucru. Pentru că indexOf este pe Array.prototype și parent.children este un NodeList, , trebuie să folosiți call(); Este cam urât, dar este un one liner și folosește funcții pe care orice dezvoltator de javascript ar trebui să le cunoască oricum.

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

Comentarii

  • var index = [].indexOf.call(child.parentNode.children, child); –  > Por cuixiping.
  • 26

  • Fwiw, folosind [] creează o instanță de Array de fiecare dată când executați acest cod, ceea ce este mai puțin eficient pentru memorie și GC față de utilizarea lui Array.prototype. –  > Por Scott Miles.
  • @ScottMiles Pot să vă rog să explicați puțin mai mult ceea ce ați spus? Nu [] se curăță în memorie ca o valoare de gunoi? –  > Por mrReiha.
  • Pentru a evalua [].indexOf motorul trebuie să creeze o instanță de matrice doar pentru a accesa valoarea indexOf implementarea pe prototip. Instanța în sine rămâne nefolosită (face GC, nu este o scurgere, ci doar irosește cicluri). Array.prototype.indexOf accesează direct acea implementare fără a aloca o instanță anonimă. Diferența va fi neglijabilă în aproape toate circumstanțele, așa că, sincer, s-ar putea să nu merite să ne pese. –  > Por Scott Miles.
  • Atenție la eroare în IE! Internet Explorer 6, 7 și 8 îl suporta, dar include în mod eronat nodurile Comment. Sursa” developer.mozilla.org/en-US/docs/Web/API/ParentNode/… –  > Por Luckylooke.
Abdennour TOUMI

ES6:

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

Explicație :

  • element.parentNode.children → Returnează frații de element, , inclusiv elementul respectiv.

  • Array.from → Prezintă constructorul lui children la un element Array obiect

  • indexOf → Se poate aplica indexOf deoarece acum aveți un element Array obiect.

Comentarii

  • Cea mai elegantă soluție, de departe 🙂 –  > Por Amit Saxena.
  • Dar este disponibilă doar din Chrome 45 și Firefox 32, și nu este disponibilă deloc pe Internet Explorer. –  > Por Peter.
  • Internet Explorer încă mai trăiește ? Doar Jock … Ok, deci aveți nevoie de un polyfill pentru a face Array.from funcționează pe Internet Explorer –  > Por Abdennour TOUMI.
  • Conform MDN, apelarea Array.from() creates a new Array instance from an array-like or iterable object. Crearea unei noi instanțe de array doar pentru a găsi un index ar putea să nu fie eficientă din punct de vedere al memoriei sau al GC, în funcție de frecvența operațiunii, caz în care iterația, așa cum se explică în răspunsul acceptat, ar fi mai ideală. –  > Por Chunk Chunky Chunk.
  • @TheDarkIn1978 Sunt conștient că există un compromis între eleganța codului &; performanțele aplicației – –  > Por Abdennour TOUMI.
philipp

ES-Shorter

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

Operatorul de răspândire este o scurtătură pentru asta

Comentarii

  • Acesta este un operator interesant. –  > Por Toți lucrătorii sunt esențiali.
  • Care este diferența dintre e.parentElement.childNodes și e.parentNode.children ? –  > Por mesqueeb.
  • childNodes include și noduri de text –  > Por philipp.
  • Cu Typescript veți obține Type 'NodeListOf<ChildNode>' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488) –  > Por ZenVentzi.
mikemaccana

Adăugarea unui (prefixat pentru siguranță) element.getParentIndex():

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

Comentarii

  • Theres un motiv pentru durerea dezvoltării web: dev’s prefix-jumpy. De ce să nu facem doar if (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }? Oricum, dacă acest lucru va fi implementat vreodată în standard în viitor, atunci va fi probabil implementat ca un getter de tipul element.parentIndex. Așadar, aș spune că cea mai bună abordare ar fi if(!Element.prototype.getParentIndex) Element.prototype.getParentIndex=Element.prototype.parentIndex?function() {return this.parentIndex}:function() {return Array.prototype.indexOf.call(this.parentNode.children, this)} –  > Por Jack Giffin.
  • Pentru că viitorul getParentIndex() poate avea o semnătură diferită de cea a implementării tale. –  > Por mikemaccana.
Jack Giffin

Presupun că, având în vedere un element în care toți copiii săi sunt ordonați secvențial în document, cea mai rapidă metodă ar trebui să fie efectuarea unei căutări binare, comparând pozițiile elementelor în document. Cu toate acestea, după cum se introduce în concluzie, ipoteza este respinsă. Cu cât aveți mai multe elemente, cu atât mai mare este potențialul de performanță. De exemplu, dacă ați avea 256 de elemente, atunci (în mod optim) ar trebui să verificați doar 16 dintre ele! Pentru 65536, doar 256! Performanța crește la puterea 2! Vedeți mai multe numere/statistici. Vizitați 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);

Apoi, modul în care se utilizează este prin obținerea proprietății „parentIndex” a oricărui element. De exemplu, consultați următorul demo.

Limitări

  • Această implementare a soluției nu va funcționa în IE8 și mai jos.

Căutare binară VS liniară pe 200.000 de elemente (ar putea bloca unele browsere mobile, ATENȚIE!):

  • În acest test, vom vedea cât timp îi ia unei căutări liniare să găsească elementul din mijloc VS o căutare binară. De ce elementul din mijloc? Pentru că se află la locația medie a tuturor celorlalte locații, deci reprezintă cel mai bine toate locațiile posibile.

Căutare binară

Căutare liniară inversă (`lastIndexOf`)

Căutare liniară înainte (`indexOf`) Căutare liniară

Căutarea prin contorizare PreviousElementSibling

Numără numărul de PreviousElementSiblings pentru a obține parentIndex.

Fără căutare

Pentru evaluarea comparativă a rezultatului testului în cazul în care browserul ar optimiza căutarea.

Conculsie

Cu toate acestea, după vizualizarea rezultatelor în Chrome, rezultatele sunt opuse celor așteptate. Căutarea liniară înainte mai proastă a fost cu un surprinzător 187 ms, 3850%, mai rapidă decât căutarea binară. Evident, Chrome a depășit cumva în mod magic console.assert și a eliminat-o prin optimizare, sau (mai optimist) Chrome utilizează în mod intern un sistem de indexare numerică pentru DOM, iar acest sistem de indexare internă este expus prin optimizările aplicate la Array.prototype.indexOf atunci când este utilizat pe un fișier HTMLCollection obiect.

Comentarii

  • Eficient, dar nepractic. –  > Por applemonkey496.
cuixiping

Utilizați algoritm de căutare binară pentru a îmbunătăți performanțele atunci când nodul are o cantitate mare de frați și surori.

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

Comentarii

  • Nu funcționează. Sunt obligat să precizez că versiunea IE și versiunea „alt browser” vor calcula rezultate diferite. Tehnica „altor browsere” funcționează așa cum era de așteptat, obținând a n-a poziție sub nodul părinte, însă tehnica IE „Retrieves the ordinal position of the object, in source order, as the object appears in the document’s all collection” ( msdn.microsoft.com/en-gb/library/ie/ms534635(v=vs.85).aspx ). De exemplu, am obținut 126 folosind tehnica „IE”, iar apoi 4 folosind cealaltă. –  > Por Christopher Bull.
1,21 gigawați

Ați putea face ceva de genul acesta:

var index = Array.prototype.slice.call(element.parentElement.children).indexOf(element);

https://developer.mozilla.org/en-US/docs/Web/API/Node/parentElement

John wantstoknow

Am avut o problemă cu nodurile de text, și arăta un index greșit. Aici este versiunea pentru a rezolva problema.

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

    return position;
}

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

Comentarii

  • Puteți explica de ce extindeți de la Element.prototype? Funcțiile par utile, dar nu știu ce fac aceste funcții (chiar dacă denumirile sunt evidente). –  > Por A1rPun.
  • @ extend Element.prototype motivul este similaritatea … 4 ex elemen.children , element.parentNode etc … așa că la fel ca și în cazul element.siblings …. metoda group este puțin complicată pentru că vreau să extind puțin abordarea sibling la elementele care se aseamănă prin același nodeType și care au aceleași atribute chiar dacă nu au același strămoș.  > Por bortunac.
  • Știu ce este extensia prototipului dar aș vrea să știu cum este folosit codul tău. el.group.value() ??. Primul meu comentariu este acolo pentru a îmbunătăți calitatea răspunsului tău. –  > Por A1rPun.
  • metodele group și siblings returnează Array cu elementele dom fondate … …. mulțumesc pentru comentariu și pentru motivul comentariului –  > Por bortunac.
  • Foarte elegant, dar și foarte lent. –  > Por Jack Giffin.
ofir_aghai
<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>

Comentarii

  • Nu aveți nevoie de jQuery aici. –  > Por Vitaly Zdanevich.
  • @VitalyZdanevich corect, dar aceasta poate fi o soluție și pentru cei care folosesc. –  > Por ofir_aghai.