Alt düğüm dizinini alın


125

Düz javascript'te (yani, jQuery, vb. Uzantı yok), tüm alt düğümleri yinelemeden ve karşılaştırmadan, bir alt düğümün ana düğümünün içindeki dizinini belirlemenin bir yolu var mı?

Örneğin,

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

Çocuğun indeksini belirlemenin daha iyi bir yolu var mı?


2
Pardon, tam bir salak mıyım? Orada burada görünüşte öğrenilen cevapların çok, ancak düğümler İhtiyacınız olmayan bütün çocuklar yaptırmak için parent.childNodesyerine parent.children?. İkincisi, yalnızca Elementsbelirli Textdüğümleri hariç tutar ... Buradaki yanıtlardan bazıları, örneğin kullanmak previousSibling, tüm alt düğümleri kullanmaya dayanır, oysa diğerleri sadece Elements ... (!)
mike kemirgen

@mikerodent Bu soruyu ilk sorduğumda amacımın ne olduğunu hatırlamıyorum ama bu farkında olmadığım önemli bir ayrıntı. Dikkatli olmadığınız sürece .childNodeskesinlikle yerine kullanılmalıdır .children. En çok gönderilen 2 cevap, belirttiğiniz gibi farklı sonuçlar verecektir.
Tüm Çalışanlar Önemlidir

1000'den fazla düğüm üzerinde binlerce arama yapmayı planlarken, düğüme bilgi ekleyin (ör. Child.dataset aracılığıyla). Amaç, bir O (n) veya O (n ^ 2) algoritmasını O (1) algoritmasına dönüştürmek olacaktır. Olumsuz tarafı, düğümler düzenli olarak eklenir ve kaldırılırsa, düğümlere eklenen ilişkili konum bilgilerinin de güncellenmesi gerekecek ve bu da herhangi bir performans kazanımı ile sonuçlanmayabilir. Ara sıra yapılan yineleme çok önemli değildir (örneğin, tıklama işleyici), ancak yinelenen yineleme sorunludur (örneğin, fare kaldırma).
CubicleSoft

Yanıtlar:


122

previousSiblingözelliği, geri dönene nullve kaç kardeşle karşılaştığınızı sayana kadar kardeşler arasında yinelemek için kullanabilirsiniz :

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

Lütfen Java gibi dillerde bir getPreviousSibling()işlev olduğunu, ancak JS'de bunun bir özellik haline geldiğini unutmayın - previousSibling.


2
Evet. Yine de metinde bir getPreviousSibling () bıraktınız.
Tim Down

7
bu yaklaşım, alt dizini belirlemek için aynı sayıda yineleme gerektirir, bu nedenle nasıl daha hızlı olacağını göremiyorum.
Michael

31
Tek satırlık sürüm:for (var i=0; (node=node.previousSibling); i++);
Scott Miles

2
@sfarbota Javascript, blok kapsamını bilmediğinden ierişilebilir olacaktır.
A1rPun

3
@nepdev Bu, .previousSiblingve arasındaki farklardan kaynaklanıyor olabilir .previousElementSibling. İlki metin düğümlerini vurur, ikincisi etmez.
abluejelly

133

Bunun indexOfiçin kullanmayı çok severim . Çünkü indexOfaçık Array.prototypeve parent.childrenbir NodeList, kullanmanız gerekiyor call();Bu biraz çirkin ama tek satırlık ve herhangi bir javascript geliştiricisinin aşina olması gereken işlevleri kullanıyor.

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

23
Fwiw, kullanmak [], bu kodu her çalıştırdığınızda bir Array örneği oluşturur; bu, bellek ve GC için kullanmaya kıyasla daha az verimlidir Array.prototype.
Scott Miles

@ScottMiles Söylediklerinizi biraz daha açıklamamı isteyebilir miyim? Does not []çöp değeri olarak belleğe temiz olsun?
mrReiha

14
[].indexOfMotoru değerlendirmek için, sadece indexOfprototip üzerindeki uygulamaya erişmek için bir dizi örneği oluşturması gerekir . Örneğin kendisi kullanılmaz (GC yapar, bu bir sızıntı değildir, sadece döngüleri boşa harcar). Array.prototype.indexOfanonim bir örnek ayırmadan bu uygulamaya doğrudan erişir. Fark hemen hemen her koşulda ihmal edilebilir olacak, bu yüzden açıkçası umursamaya değmeyebilir.
Scott Miles

3
IE'deki hatalara dikkat edin! Internet Explorer 6, 7 ve 8 bunu destekledi, ancak yanlışlıkla Yorum düğümleri içeriyor. Kaynak " developer.mozilla.org/en-US/docs/Web/API/ParentNode/…
Luckylooke

103

ES6:

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

Açıklama:

  • element.parentNode.childrenelementBu element dahil olmak üzere kardeşlerini verir .

  • Array.from→ Yapıcısını childrenbir Arraynesneye atar

  • indexOf→ Artık indexOfbir Arraynesneniz olduğu için başvurabilirsiniz .


2
Şimdiye kadarki en zarif çözüm :)
Amit Saxena

1
Ancak yalnızca Chrome 45 ve Firefox 32'de mevcuttur ve Internet Explorer'da hiç kullanılamaz.
Peter

Internet Explorer hala yaşıyor mu? Sadece Jock .. Tamam sen lüzum böylece polyfill yapmak için Array.fromInternet Explorer üzerinde çalışmalarını
Abdennour TOUMI

9
MDN'ye göre, Array.from () 'u çağırmak creates a new Array instance from an array-like or iterable object.Sadece bir dizin bulmak için yeni bir dizi örneği oluşturmak, işlemin ne sıklıkta olduğuna bağlı olarak bellek veya GC verimli olmayabilir, bu durumda kabul edilen yanıtta açıklandığı gibi yineleme daha fazla olur ideal.
TheDarkIn1978

1
@ TheDarkIn1978 Kod zarafeti ve uygulama performansı arasında bir değiş tokuş olduğunun farkındayım 👍🏻
Abdennour TOUMI

39

ES-Daha kısa

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

Yayılma Operatörü bunun için bir kısayoldur


Bu ilginç bir operatör.
Tüm Çalışanlar Önemlidir

Arasındaki fark nedir e.parentElement.childNodesve e.parentNode.children?
mesqueeb

4
childNodesmetin düğümlerini de içerir
philipp

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

9

Bir (güvenlik için önekli) element.getParentIndex () ekleme:

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

1
Web geliştirmenin zahmetli olmasının bir nedeni var: önek atlamalı geliştiriciler. Neden sadece yapmıyorsun if (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }? Her neyse, eğer bu gelecekte standarda uygulanırsa, o zaman muhtemelen alıcı gibi uygulanacaktır element.parentIndex. Bu yüzden, en iyi yaklaşımın şu olacağını söyleyebilirimif(!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
Çünkü gelecekte getParentIndex()uygulamanızdan farklı bir imza olabilir.
mikemaccana

7

𝗣𝗿𝗼𝗼𝗳 𝗢𝗳 𝗔 𝗟𝗲𝘀𝘀 𝗘𝗳𝗳𝗶𝗰𝗶𝗲𝗻𝘁 𝗕𝗶𝗻𝗮𝗿𝘆 𝗦𝗲𝗮𝗿𝗰𝗵

Tüm alt öğelerinin belge üzerinde sırayla sıralandığı bir öğe verildiğinde, en hızlı yol, öğelerin belge konumlarını karşılaştırarak ikili arama yapmak olmalıdır. Bununla birlikte, sonuçta ortaya konulduğu gibi, hipotez reddedilmiştir. Ne kadar çok öğeye sahip olursanız, performans potansiyeli o kadar artar. Örneğin, 256 öğeniz varsa, o zaman (optimal olarak) yalnızca 16 öğeyi kontrol etmeniz gerekir! 65536 için sadece 256! Performans 2'nin gücüne yükseliyor! Daha fazla sayı / istatistik görün. Wikipedia'yı ziyaret edin

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

Daha sonra, onu kullanma şekliniz, herhangi bir elemanın 'parentIndex' özelliğini almaktır. Örneğin, aşağıdaki demoyu inceleyin.

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

Sınırlamalar

  • Çözümün bu uygulaması IE8 ve altında çalışmayacaktır.

İkili ve Doğrusal Arama 200.000 öğede (bazı mobil tarayıcıları çökertebilir, DİKKAT!):

  • Bu testte, doğrusal bir aramanın orta elemanı VS bir ikili arama bulmasının ne kadar sürdüğünü göreceğiz. Neden orta unsur? Diğer tüm konumların ortalama konumunda olduğundan, tüm olası konumları en iyi şekilde temsil eder.

Ikili arama

Geriye doğru (`lastIndexOf`) Doğrusal Arama

İleri ("indexOf`) Doğrusal Arama

ÖncekiElementBibling Counter Search

ParentIndex öğesini almak için PreviousElementSiblings sayısını sayar.

Arama Yok

Tarayıcının aramayı optimize etmesi durumunda testin sonucunun ne olacağını karşılaştırmak için.

Conculsion

Ancak sonuçları Chrome'da görüntüledikten sonra, sonuçlar beklenenin tam tersi oldu. Aptal ileri doğrusal arama şaşırtıcı bir şekilde 187 ms,% 3850, ikili aramadan daha hızlıydı. Açıktır ki, Chrome bir şekilde sihirli bir şekilde altını çizdi console.assertve onu optimize etti veya (daha iyimser bir şekilde) Chrome, DOM için dahili olarak sayısal indeksleme sistemini kullanıyor ve bu dahili indeksleme sistemi, Array.prototype.indexOfbir HTMLCollectionnesne üzerinde kullanıldığında uygulanan optimizasyonlarla ortaya çıkıyor .


Etkili ama pratik değil.
applemonkey496

3

Düğümün büyük miktarda kardeşleri olduğunda performansı iyileştirmek için ikili arama algoritmasını kullanın .

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

Çalışmıyor. IE sürümünün ve "diğer tarayıcı" sürümünün farklı sonuçları hesaplayacağını belirtmek zorundayım. "Diğer tarayıcılar" tekniği beklendiği gibi çalışır, üst düğüm altında n'inci konumu alır, ancak IE tekniği "Nesne belgenin tüm koleksiyonunda göründüğü için nesnenin sıralı konumunu kaynak sırasına göre alır" ( msdn.microsoft .com / en-gb / library / ie / ms534635 (v = vs.85) .aspx ). Örneğin "IE" tekniğini kullanarak 126, diğerini kullanarak 4 aldım.
Christopher Bull

1

Metin düğümlerinde sorun yaşadım ve yanlış dizin gösteriyordu. İşte onu düzeltmek için sürüm.

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

eski

/* 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
Neden uzandığınızı açıklayabilir misiniz Element.prototype? İşlevler kullanışlı görünüyor ancak bu işlevlerin ne işe yaradığını bilmiyorum (adlar açık olsa bile).
A1rPun

@ expand Element.prototype nedeni benzerliktir ... 4 ex elemen.children, element.parentNode vb ... yani element.siblings gibi ... grup yöntemi biraz karmaşık çünkü genişletmek istiyorum aynı nodeType ile benzer özelliklere sahip ve aynı ataya sahip olmasa bile aynı özelliklere sahip olan küçük kardeş yaklaşımı
bortunac

Prototip genişletmenin ne olduğunu biliyorum ama kodunuzun nasıl kullanıldığını bilmek hoşuma gidiyor. el.group.value()??. İlk yorumum cevabınızın kalitesini artırmak için var.
A1rPun

grup ve kardeş metotları, kurulmuş dom elemanlarına sahip Dizi döndürür .. .... yorumunuz ve yorumunuz için teşekkürler
bortunac

Çok zarif ama aynı zamanda çok yavaş.
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>

Burada jQuery'ye ihtiyacınız yok.
Vitaly Zdanevich
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.