Dapatkan indeks node anak

125

Dalam javascript lurus (yaitu, tidak ada ekstensi seperti jQuery, dll.), Apakah ada cara untuk menentukan indeks simpul anak di dalam simpul induknya tanpa mengulang dan membandingkan semua simpul anak?

Misalnya,

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

Apakah ada cara yang lebih baik untuk menentukan indeks anak?

Semua Pekerja Sangat Penting
sumber
2
Maaf, apakah saya benar-benar bodoh? Ada banyak jawaban yang tampaknya dipelajari di sini, tetapi untuk mendapatkan semua node turunan tidak perlu Anda lakukan parent.childNodes, bukan parent.children?. Yang terakhir hanya mencantumkan Elements, tidak termasuk di Textsimpul tertentu ... Beberapa jawaban di sini, misalnya menggunakan previousSibling, didasarkan pada penggunaan semua simpul anak, sedangkan yang lain hanya terganggu dengan anak-anak yang Elements ... (!)
mike rodent
@mikerodent Saya tidak ingat apa tujuan saya ketika saya pertama kali mengajukan pertanyaan ini, tetapi itu adalah detail utama yang tidak saya sadari. Kecuali Anda berhati-hati, .childNodespasti harus digunakan sebagai pengganti .children. 2 jawaban teratas yang diposting akan memberikan hasil yang berbeda seperti yang Anda tunjukkan.
Semua Pekerja Penting
Ketika berencana melakukan ribuan pencarian di lebih dari 1000 node, maka lampirkan informasi ke node tersebut (misalnya melalui child.dataset). Tujuannya adalah untuk mengubah algoritma O (n) atau O (n ^ 2) menjadi algoritma O (1). Kelemahannya adalah jika node ditambahkan dan dihapus secara teratur, informasi posisi terkait yang dilampirkan ke node juga harus diperbarui, yang mungkin tidak menghasilkan peningkatan kinerja apa pun. Iterasi sesekali bukan masalah besar (mis. Click handler) tetapi iterasi berulang bermasalah (mis. Mousemove).
CubicleSoft

Jawaban:

122

Anda bisa menggunakan previousSiblingproperti untuk mengulang kembali saudara kandung sampai Anda kembali nulldan menghitung berapa banyak saudara yang Anda temui:

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

Harap dicatat bahwa dalam bahasa seperti Java, ada getPreviousSibling()fungsi, namun di JS ini telah menjadi properti - previousSibling.

Liv
sumber
2
Ya. Anda meninggalkan getPreviousSibling () di teks.
Tim Down
7
pendekatan ini membutuhkan jumlah iterasi yang sama untuk menentukan indeks anak, jadi saya tidak bisa melihat bagaimana itu akan lebih cepat.
Michael
31
Versi satu baris:for (var i=0; (node=node.previousSibling); i++);
Scott Miles
2
@sfarbota Javascript tidak mengetahui pelingkupan blok, jadi iakan dapat diakses.
A1rPun
3
@nepdev Hal itu mungkin terjadi karena perbedaan antara .previousSiblingdan .previousElementSibling. Yang pertama mengenai node teks, yang terakhir tidak.
abluejelly
133

Saya menjadi suka menggunakan indexOfuntuk ini. Karena indexOfaktif Array.prototypedan parent.childrenmerupakan NodeList, Anda harus menggunakan call();Ini agak jelek tetapi ini adalah satu liner dan menggunakan fungsi yang harus dipahami oleh setiap pengembang javascript.

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);
KhalilRavanna
sumber
7
var index = [] .indexOf.call (child.parentNode.children, anak);
cuixiping
23
Fwiw, menggunakan []membuat instance Array setiap kali Anda menjalankan kode itu, yang kurang efisien untuk memori dan GC vs penggunaan Array.prototype.
Scott Miles
@ Scottsley Bolehkah saya meminta penjelasan lebih lanjut tentang apa yang Anda katakan? Tidak []membersihkan memori sebagai nilai sampah?
mrReiha
14
Untuk mengevaluasi [].indexOfmesin harus membuat turunan array hanya untuk mengakses indexOfimplementasi pada prototipe. Instance itu sendiri tidak digunakan (melakukan GC, ini bukan kebocoran, ini hanya membuang-buang siklus). Array.prototype.indexOfmengakses implementasi itu secara langsung tanpa mengalokasikan instance anonim. Perbedaan tersebut akan dapat diabaikan di hampir semua keadaan, jadi terus terang mungkin tidak ada gunanya untuk diperhatikan.
Scott Miles
3
Waspadai kesalahan di IE! Internet Explorer 6, 7 dan 8 mendukungnya, tetapi secara keliru menyertakan node Komentar. Sumber " developer.mozilla.org/en-US/docs/Web/API/ParentNode/…
Luckylooke
102

ES6:

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

Penjelasan:

  • element.parentNode.children→ Menampilkan saudara dari element, termasuk elemen itu.

  • Array.from→ Melemparkan konstruktor dari childrensebuah Arrayobjek

  • indexOf→ Anda dapat melamar indexOfkarena sekarang Anda memiliki Arrayobjek.

Abdennour TOUMI
sumber
2
Solusi paling elegan, sejauh ini :)
Amit Saxena
1
Tetapi hanya tersedia dari Chrome 45 dan Firefox 32, dan tidak tersedia di Internet Explorer sama sekali.
Peter
Internet Explorer masih hidup? Just Jock .. Ok jadi Anda memerlukan polyfill untuk membuat Array.fromkarya di Internet explorer
Abdennour TOUMI
9
Menurut MDN, memanggil Array.from () creates a new Array instance from an array-like or iterable object. Membuat instance array baru hanya untuk menemukan indeks mungkin bukan memori atau GC yang efisien, bergantung pada seberapa sering operasi, dalam hal ini iterasi, seperti yang dijelaskan dalam jawaban yang diterima, akan lebih banyak ideal.
TheDarkIn1978
1
@ TheDarkIn1978 Saya sadar ada trade-off antara keanggunan kode & kinerja aplikasi 👍🏻
Abdennour TOUMI
38

ES — Lebih pendek

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

Operator penyebaran adalah jalan pintas untuk itu

philipp
sumber
Itu operator yang menarik.
Semua Pekerja Penting
Apa perbedaan antara e.parentElement.childNodesdan e.parentNode.children?
mesqueeb
4
childNodesmenyertakan node teks juga
philipp
Dengan Ketikan yang Anda dapatkan Type 'NodeListOf<ChildNode>' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)
ZenVentzi
9

Menambahkan elemen (diawali untuk keselamatan ).getParentIndex ():

Element.prototype.PREFIXgetParentIndex = function() {
  return Array.prototype.indexOf.call(this.parentNode.children, this);
}
mikemaccana
sumber
1
Ada alasan untuk kesusahan pengembangan web: prefix-jumpy dev's. Kenapa tidak lakukan saja if (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }? Bagaimanapun, jika ini pernah diimplementasikan menjadi standar di masa depan maka kemungkinan akan diterapkan sebagai getter like element.parentIndex. Jadi, menurut saya pendekatan terbaik adalahif(!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
Karena masa depan getParentIndex()mungkin memiliki tanda tangan yang berbeda dari implementasi Anda.
mikemaccana
7

Saya berhipotesis bahwa dengan adanya elemen di mana semua anaknya diurutkan pada dokumen secara berurutan, cara tercepat adalah melakukan pencarian biner, membandingkan posisi dokumen dari elemen tersebut. Namun, seperti yang diperkenalkan dalam kesimpulan, hipotesis ditolak. Semakin banyak elemen yang Anda miliki, semakin besar potensi kinerja. Misalnya, jika Anda memiliki 256 elemen, maka (secara optimal) Anda hanya perlu memeriksa 16 elemen saja! Untuk 65536, hanya 256! Performa berkembang menjadi kekuatan 2! Lihat lebih banyak angka / statistik. Kunjungi 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);

Lalu, cara Anda menggunakannya adalah dengan mendapatkan properti 'parentIndex' dari elemen apa pun. Misalnya, lihat demo berikut.

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

Batasan

  • Penerapan solusi ini tidak akan berfungsi di IE8 dan di bawahnya.

Pencarian Biner VS Linear Pada 200 ribu elemen (mungkin merusak beberapa browser seluler, WASPADALAH!):

  • Dalam pengujian ini, kita akan melihat berapa lama waktu yang dibutuhkan untuk pencarian linier untuk menemukan elemen tengah VS pencarian biner. Mengapa elemen tengah? Karena ini berada di lokasi rata-rata dari semua lokasi lain, jadi ini paling mewakili semua lokasi yang memungkinkan.

Pencarian Biner

Pencarian Linear Mundur (`lastIndexOf`)

Meneruskan (`indexOf`) Linear Search

Pencarian Konter PreviousElementSibling

Menghitung jumlah PreviousElementSiblings untuk mendapatkan parentIndex.

Tidak Ada Pencarian

Untuk pembandingan apa hasil dari pengujian jika browser mengoptimalkan pencarian.

Conculsion

Namun, setelah melihat hasilnya di Chrome, hasilnya berlawanan dengan yang diharapkan. Pencarian linier ke depan yang lebih bodoh ternyata 187 ms, 3850%, lebih cepat daripada pencarian biner. Terbukti, Chrome entah bagaimana secara ajaib mengakali console.assertdan mengoptimalkannya, atau (lebih optimis) Chrome secara internal menggunakan sistem pengindeksan numerik untuk DOM, dan sistem pengindeksan internal ini diekspos melalui pengoptimalan yang diterapkan Array.prototype.indexOfsaat digunakan pada HTMLCollectionobjek.

Jack Giffin
sumber
Efisien, tetapi tidak praktis.
applemonkey496
3

Gunakan algoritme penelusuran biner untuk meningkatkan performa saat node memiliki saudara dalam jumlah besar.

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;
}
cuixiping
sumber
Tidak berhasil. Saya harus menunjukkan bahwa versi IE dan versi "browser lain" akan menghitung hasil yang berbeda. Teknik "browser lain" bekerja seperti yang diharapkan, mendapatkan posisi ke-n di bawah node induk, namun teknik IE "Mengambil posisi ordinal objek, dalam urutan sumber, saat objek muncul di semua kumpulan dokumen" ( msdn.microsoft .com / en-gb / library / ie / ms534635 (v = vs.85) .aspx ). Misalnya saya mendapat 126 menggunakan teknik "IE", dan kemudian 4 menggunakan yang lain.
Christopher Bull
1

Saya memiliki masalah dengan node teks, dan itu menunjukkan indeks yang salah. Ini versi untuk memperbaikinya.

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

    return position;
}
John ingin tahu sekarang
sumber
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());
     });
bortunac.dll
sumber
1
Bisakah Anda menjelaskan mengapa Anda memperpanjang Element.prototype? Fungsi terlihat berguna tapi saya tidak tahu apa fungsi fungsi ini (bahkan jika penamaannya jelas).
A1rPun
@ expand Element.prototype alasannya adalah kesamaan ... 4 ex elemen.children, element.parentNode dll ... jadi dengan cara yang sama Anda menangani element.siblings .... metode grup agak rumit karena saya ingin memperpanjang sedikit pendekatan saudara ke elelemts sama dengan nodeType yang sama dan memiliki atribut yang sama bahkan tidak memiliki leluhur yang sama
bortunac
Saya tahu apa prototipe perluasan itu tetapi saya ingin tahu bagaimana kode Anda digunakan. el.group.value()??. Komentar pertama saya ada untuk meningkatkan kualitas jawaban Anda.
A1rPun
metode grup dan saudara kandung mengembalikan Array dengan elemen dom yang didirikan .. .... terima kasih atas komentar Anda dan untuk alasan komentar
bortunac
Sangat elegan, namun juga sangat lambat.
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>
ofir_aghai
sumber
Anda tidak perlu jQuery di sini.
Vitaly Zdanevich