Ottieni l'indice del nodo figlio


125

In javascript diretto (cioè, senza estensioni come jQuery, ecc.), C'è un modo per determinare l'indice di un nodo figlio all'interno del suo nodo padre senza iterare e confrontare tutti i nodi figli?

Per esempio,

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

C'è un modo migliore per determinare l'indice del bambino?


2
Scusa, sono un completo idiota? Ci sono molte risposte apparentemente apprese qui, ma per ottenere tutti i nodi figli non è necessario fare parent.childNodes, piuttosto che parent.children?. Quest'ultimo elenca solo i nodi Elements, escludendo in particolare Text... Alcune delle risposte qui, ad esempio usando previousSibling, si basano sull'uso di tutti i nodi figli, mentre altri si preoccupano solo dei bambini che sono Elements ... (!)
mike rodent

@mikerodent Non ricordo quale fosse il mio scopo quando inizialmente ho posto questa domanda, ma è un dettaglio chiave di cui non ero a conoscenza. A meno che tu non stia attento, .childNodesdovrebbe assolutamente essere usato al posto di .children. Le prime 2 risposte pubblicate daranno risultati diversi come hai sottolineato.
Tutti i lavoratori sono essenziali

Quando si pianifica di eseguire migliaia di ricerche su oltre 1000 nodi, allegare le informazioni al nodo (ad esempio tramite child.dataset). L'obiettivo sarebbe convertire un algoritmo O (n) o O (n ^ 2) in un algoritmo O (1). Lo svantaggio è che se i nodi vengono aggiunti e rimossi regolarmente, anche le informazioni sulla posizione associate allegate ai nodi dovranno essere aggiornate, il che potrebbe non comportare alcun miglioramento delle prestazioni. L'iterazione occasionale non è un grosso problema (ad es. Gestore di clic) ma l'iterazione ripetuta è problematica (ad es. Mousemove).
CubicleSoft

Risposte:


122

puoi utilizzare la previousSiblingproprietà per scorrere i fratelli fino a quando non torni nulle conta quanti fratelli hai incontrato:

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

Si noti che in linguaggi come Java esiste una getPreviousSibling()funzione, tuttavia in JS questa è diventata una proprietà - previousSibling.


2
Sì. Tuttavia, hai lasciato una getPreviousSibling () nel testo.
Tim Down

7
questo approccio richiede lo stesso numero di iterazioni per determinare l'indice figlio, quindi non riesco a vedere come sarebbe molto più veloce.
Michael

31
Versione a una riga:for (var i=0; (node=node.previousSibling); i++);
Scott Miles

2
@sfarbota Javascript non conosce l'ambito dei blocchi, quindi isarà accessibile.
A1rPun

3
@nepdev Ciò sarebbe dovuto alle differenze tra .previousSiblinge .previousElementSibling. Il primo colpisce i nodi di testo, il secondo no.
abluejelly

133

Mi sono appassionato all'uso indexOfper questo. Perché indexOfè attivo Array.prototypeed parent.childrenè un NodeList, devi usare call();È un po 'brutto ma è una riga e usa funzioni che qualsiasi sviluppatore di javascript dovrebbe avere comunque familiarità.

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, using []crea un'istanza di Array ogni volta che si esegue quel codice, che è meno efficiente per memoria e GC rispetto all'utilizzo Array.prototype.
Scott Miles

@ScottMiles Posso chiederti di spiegare un po 'di più quello che hai detto? Non []viene pulita la memoria come valore di spazzatura?
mrReiha

14
Per valutare [].indexOfil motore deve creare un'istanza di array solo per accedere indexOfall'implementazione sul prototipo. L'istanza stessa non viene utilizzata (fa GC, non è una perdita, sta solo sprecando cicli). Array.prototype.indexOfaccede a tale implementazione direttamente senza allocare un'istanza anonima. La differenza sarà trascurabile in quasi tutte le circostanze, quindi francamente potrebbe non valerne la pena.
Scott Miles

3
Attenzione agli errori in IE! Internet Explorer 6, 7 e 8 lo supportavano, ma include erroneamente i nodi Comment. Fonte " developer.mozilla.org/en-US/docs/Web/API/ParentNode/…
Luckylooke

102

ES6:

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

Spiegazione :

  • element.parentNode.children→ Restituisce i fratelli di element, incluso quell'elemento.

  • Array.from→ Esegue il cast del costruttore di childrensu un Arrayoggetto

  • indexOf→ Puoi candidarti indexOfperché ora hai un Arrayoggetto.


2
Soluzione più elegante, di gran lunga :)
Amit Saxena

1
Ma disponibile solo da Chrome 45 e Firefox 32 e non è affatto disponibile su Internet Explorer.
Peter

Internet Explorer è ancora vivo? Just Jock .. Ok quindi hai bisogno di un polyfill per far Array.fromfunzionare Internet Explorer
Abdennour TOUMI

9
Secondo MDN, chiamare Array.from () La creates a new Array instance from an array-like or iterable object. creazione di una nuova istanza di array solo per trovare un indice potrebbe non essere efficiente in termini di memoria o GC, a seconda della frequenza con cui l'operazione, nel qual caso l'iterazione, come spiegato nella risposta accettata, sarebbe più ideale.
TheDarkIn1978

1
@ TheDarkIn1978 So che c'è un compromesso tra l'eleganza del codice e le prestazioni delle app 👍🏻
Abdennour TOUMI

38

ES-Shorter

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

L'operatore spread è una scorciatoia per questo


Questo è un operatore interessante.
Tutti i lavoratori sono essenziali

Qual è la differenza tra e.parentElement.childNodese e.parentNode.children?
mesqueeb

4
childNodesinclude anche nodi di testo
philipp

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

9

Aggiunta di un element.getParentIndex () (prefisso per sicurezza):

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

1
C'è una ragione per la sofferenza dello sviluppo web: prefix-jumpy dev's. Perché non farlo e basta if (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }? Ad ogni modo, se questo verrà implementato nello standard in futuro, sarà probabilmente implementato come un getter element.parentIndex. Quindi, direi che l'approccio migliore sarebbeif(!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
Perché il futuro getParentIndex()potrebbe avere una firma diversa dalla tua implementazione.
mikemaccana

7

Ipotizzo che dato un elemento in cui tutti i suoi figli sono ordinati sequenzialmente sul documento, il modo più veloce dovrebbe essere quello di fare una ricerca binaria, confrontando le posizioni del documento degli elementi. Tuttavia, come introdotto nella conclusione, l'ipotesi è respinta. Più elementi hai, maggiore è il potenziale di prestazioni. Ad esempio, se avessi 256 elementi, allora (in modo ottimale) dovresti controllarne solo 16! Per 65536, solo 256! Le prestazioni crescono fino alla potenza di 2! Vedi più numeri / statistiche. Visita 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);

Quindi, il modo in cui lo usi è ottenere la proprietà "parentIndex" di qualsiasi elemento. Ad esempio, controlla la seguente demo.

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

limitazioni

  • Questa implementazione della soluzione non funzionerà in IE8 e versioni precedenti.

Ricerca binaria VS lineare su 200 mila elementi (potrebbero causare il crash di alcuni browser mobili, ATTENZIONE!):

  • In questo test, vedremo quanto tempo impiega una ricerca lineare per trovare l'elemento centrale VS una ricerca binaria. Perché l'elemento centrale? Perché è nella posizione media di tutte le altre posizioni, quindi rappresenta al meglio tutte le posizioni possibili.

Ricerca binaria

Ricerca lineare all'indietro (`lastIndexOf`)

Ricerca lineare in avanti (`indexOf`)

PreviousElementSibling Counter Search

Conta il numero di PreviousElementSiblings per ottenere parentIndex.

Nessuna ricerca

Per il benchmarking quale sarebbe il risultato del test se il browser ottimizzasse la ricerca.

La conculsione

Tuttavia, dopo aver visualizzato i risultati in Chrome, i risultati sono l'opposto di quanto previsto. La più stupida ricerca lineare in avanti è stata sorprendente di 187 ms, 3850%, più veloce della ricerca binaria. Evidentemente, Chrome in qualche modo ha magicamente superato in astuzia console.asserte ottimizzato, o (più ottimisticamente) Chrome utilizza internamente il sistema di indicizzazione numerica per il DOM, e questo sistema di indicizzazione interno è esposto attraverso le ottimizzazioni applicate Array.prototype.indexOfquando viene utilizzato su un HTMLCollectionoggetto.


Efficiente, ma poco pratico.
applemonkey496

3

Usa l' algoritmo di ricerca binaria per migliorare le prestazioni quando il nodo ha fratelli di grandi quantità.

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

Non funziona Sono obbligato a sottolineare che la versione di IE e la versione "altro browser" calcoleranno risultati diversi. La tecnica "altri browser" funziona come previsto, ottenendo l'ennesima posizione sotto il nodo padre, tuttavia la tecnica IE "Recupera la posizione ordinale dell'oggetto, in ordine di origine, come l'oggetto appare nella raccolta di tutti i documenti" ( msdn.microsoft .com / en-gb / library / ie / ms534635 (v = vs.85) .aspx ). Ad esempio, ho ottenuto 126 utilizzando la tecnica "IE", e poi 4 utilizzando l'altra.
Christopher Bull

1

Ho avuto problemi con i nodi di testo e mostrava un indice sbagliato. Ecco la versione per risolverlo.

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
Puoi spiegare perché ti estendi da Element.prototype? Le funzioni sembrano utili ma non so cosa fanno queste funzioni (anche se i nomi sono ovvi).
A1rPun

@ extended Element.prototype il motivo è la somiglianza ... 4 ex elemen.children, element.parentNode ecc ... quindi nello stesso modo in cui ti rivolgi a element.siblings .... il metodo di gruppo è un po 'complicato perché voglio estendere un po 'il fratello si avvicina agli elelemts allo stesso modo dallo stesso nodeType e con gli stessi attributi anche senza lo stesso antenato
bortunac

So cos'è l'estensione del prototipo ma mi piace sapere come viene utilizzato il tuo codice. el.group.value()??. Il mio primo commento è lì per migliorare la qualità della tua risposta.
A1rPun

i metodi group e siblings restituiscono Array con elementi dom fondati .. .... grazie per il tuo commento e per il motivo del commento
bortunac

Molto elegante, ma anche molto lento.
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>

Non hai bisogno di jQuery qui.
Vitaly Zdanevich
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.