รับดัชนีโหนดลูก


125

ในจาวาสคริปต์แบบตรง (กล่าวคือไม่มีส่วนขยายเช่น jQuery เป็นต้น) มีวิธีกำหนดดัชนีของโหนดลูกภายในโหนดแม่โดยไม่ต้องวนซ้ำและเปรียบเทียบโหนดลูกทั้งหมดหรือไม่

เช่น,

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

มีวิธีที่ดีกว่าในการกำหนดดัชนีของเด็กหรือไม่?


2
ขอโทษทีฉันเป็น Dolt สมบูรณ์หรือเปล่า มีคำตอบที่ได้เรียนรู้มากมายที่นี่ แต่เพื่อให้ได้โหนดลูกทั้งหมดคุณไม่จำเป็นต้องทำparent.childNodesแทนที่จะทำอย่างนั้นparent.children? หลังแสดงรายการเท่านั้นElementsยกเว้นในTextโหนดเฉพาะ... คำตอบบางส่วนที่นี่เช่นการใช้previousSiblingจะขึ้นอยู่กับการใช้โหนดลูกทั้งหมดในขณะที่คนอื่น ๆ จะใส่ใจกับเด็กที่เป็นElement... (!)
ไมค์หนู

@mikerodent ฉันจำไม่ได้ว่าจุดประสงค์ของฉันคืออะไรเมื่อฉันถามคำถามนี้ในตอนแรก แต่นั่นเป็นรายละเอียดสำคัญที่ฉันไม่รู้ ยกเว้นกรณีที่คุณระวังแน่นอนควรนำมาใช้แทน.childNodes .childrenคำตอบที่โพสต์ 2 อันดับแรกจะให้ผลลัพธ์ที่แตกต่างกันตามที่คุณระบุ
พนักงานทุกคนมีความจำเป็นใน

เมื่อวางแผนที่จะทำการค้นหาหลายพันรายการมากกว่า 1,000 โหนดขึ้นไปให้แนบข้อมูลไปยังโหนด (เช่นผ่าน child.dataset) เป้าหมายคือการแปลงอัลกอริทึม O (n) หรือ O (n ^ 2) เป็นอัลกอริทึม O (1) ข้อเสียคือหากมีการเพิ่มและลบโหนดเป็นประจำข้อมูลตำแหน่งที่เกี่ยวข้องที่แนบมากับโหนดจะต้องได้รับการอัปเดตด้วยซึ่งอาจไม่ส่งผลให้ประสิทธิภาพเพิ่มขึ้น การทำซ้ำเป็นครั้งคราวไม่ใช่เรื่องใหญ่ (เช่นตัวจัดการการคลิก) แต่การทำซ้ำซ้ำ ๆ เป็นปัญหา (เช่นการเคลื่อนย้ายเมาส์)
CubicleSoft

คำตอบ:


122

คุณสามารถใช้previousSiblingพร็อพเพอร์ตี้เพื่อวนกลับผ่านพี่น้องจนกว่าคุณจะกลับมาnullและนับจำนวนพี่น้องที่คุณพบ:

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

โปรดทราบว่าในภาษาเช่น Java มีgetPreviousSibling()ฟังก์ชั่น แต่ใน JS นี้ได้กลายเป็นสถานที่ให้บริการ previousSibling-


2
อ๋อ คุณทิ้ง getPreviousSibling () ไว้ในข้อความแล้ว
Tim Down

7
วิธีนี้ต้องการการทำซ้ำจำนวนเท่ากันเพื่อกำหนดดัชนีลูกดังนั้นฉันจึงไม่เห็นว่ามันจะเร็วกว่านี้ได้อย่างไร
Michael

31
เวอร์ชันบรรทัดเดียว:for (var i=0; (node=node.previousSibling); i++);
Scott Miles

2
@sfarbota Javascript ไม่ทราบขอบเขตการบล็อกดังนั้นiจะสามารถเข้าถึงได้
A1rPun

3
@nepdev นั่นอาจเป็นเพราะความแตกต่างระหว่าง.previousSiblingและ.previousElementSibling. อดีตนิยมโหนดข้อความอย่างหลังไม่ได้
abluejelly

133

ฉันชอบใช้indexOfสำหรับสิ่งนี้ เนื่องจากindexOfเปิดอยู่Array.prototypeและparent.childrenเป็น a NodeListคุณจึงต้องใช้call();มันค่อนข้างน่าเกลียด แต่มันเป็นซับเดียวและใช้ฟังก์ชั่นที่นักพัฒนาจาวาสคริปต์ทุกคนควรคุ้นเคย

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 = [] .indexOf.call (child.parentNode.children เด็ก);
cuixiping

23
FWIW ใช้[]สร้างอินสแตนซ์อาร์เรย์ทุกครั้งที่คุณเรียกใช้รหัสที่ซึ่งมีประสิทธิภาพน้อยลงสำหรับหน่วยความจำและ GC Array.prototypeเทียบกับการใช้
Scott Miles

@ScottMiles ฉันขอให้อธิบายสิ่งที่คุณพูดอีกหน่อยได้ไหม ไม่ได้[]รับการทำความสะอาดหน่วยความจำเป็นค่าขยะ?
mrReiha

14
ในการประเมินเอ็น[].indexOfจิ้นจะต้องสร้างอินสแตนซ์อาร์เรย์เพื่อเข้าถึงการindexOfใช้งานบนต้นแบบ อินสแตนซ์นั้นไม่ได้ใช้งาน (มันทำ GC ไม่ใช่การรั่วไหลมันเป็นแค่การสูญเสียรอบ) Array.prototype.indexOfเข้าถึงการใช้งานนั้นโดยตรงโดยไม่ต้องจัดสรรอินสแตนซ์ที่ไม่ระบุตัวตน ความแตกต่างจะไม่สำคัญในเกือบทุกสถานการณ์ดังนั้นตรงไปตรงมามันอาจไม่คุ้มค่าที่จะใส่ใจ
Scott Miles

3
ระวังข้อผิดพลาดใน IE! Internet Explorer 6, 7 และ 8 ได้รับการสนับสนุน แต่มีโหนดข้อคิดเห็นผิดพลาด ที่มา " developer.mozilla.org/en-US/docs/Web/API/ParentNode/…
Luckylooke

103

ES6:

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

คำอธิบาย:

  • element.parentNode.children→ส่งคืนพี่น้องของelementรวมทั้งองค์ประกอบนั้น

  • Array.from→ร่ายคอนสตรัคเตอร์ของchildrenไปยังArrayวัตถุ

  • indexOf→คุณสามารถสมัครได้indexOfเพราะตอนนี้คุณมีArrayวัตถุ


2
ทางออกที่ดีที่สุดโดยตอนนี้ :)
Amit Saxena

1
แต่ใช้ได้เฉพาะจาก Chrome 45 และ Firefox 32 และไม่มีให้บริการบน Internet Explorer เลย
Peter

Internet Explorer ยังมีชีวิตอยู่? เพียงแค่จ๊อค .. ตกลงดังนั้นคุณจำเป็นต้องมีpolyfillที่จะทำให้Array.fromการทำงานบน Internet Explorer
Abdennour Toumi

9
ตาม MDN เรียกArray.from () creates a new Array instance from an array-like or iterable object.การสร้างอินสแตนซ์อาร์เรย์ใหม่เพียงเพื่อค้นหาดัชนีอาจไม่มีหน่วยความจำหรือ GC มีประสิทธิภาพขึ้นอยู่กับความถี่ในการดำเนินการซึ่งในกรณีนี้การทำซ้ำตามที่อธิบายไว้ในคำตอบที่ยอมรับจะมีมากกว่า ในอุดมคติ.
TheDarkIn1978

1
@ TheDarkIn1978 ฉันทราบว่ามีการแลกเปลี่ยนระหว่างความสง่างามของโค้ดและประสิทธิภาพของแอป👍🏻
Abdennour TOUMI

39

ES-สั้น

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

ตัวดำเนินการแพร่กระจายเป็นทางลัดสำหรับสิ่งนั้น


นั่นเป็นตัวดำเนินการที่น่าสนใจ
แรงงานทุกคนมีความจำเป็น

อะไรคือความแตกต่างระหว่างe.parentElement.childNodesและe.parentNode.children?
mesqueeb

4
childNodesรวมถึงโหนดข้อความด้วย
ฟิลิปปินส์

ด้วย typescript คุณจะได้รับ Type 'NodeListOf<ChildNode>' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)
ZenVentzi

9

การเพิ่มองค์ประกอบ (นำหน้าเพื่อความปลอดภัย) getParentIndex ():

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

1
มีเหตุผลสำหรับความเจ็บปวดของการพัฒนาเว็บ: คำนำหน้า - นักพัฒนาที่น่ากลัว ทำไมไม่ทำif (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }? อย่างไรก็ตามหากสิ่งนี้ถูกนำมาใช้เป็นมาตรฐานในอนาคตก็มีแนวโน้มที่จะถูกนำไปใช้ในลักษณะที่เหมือนelement.parentIndexกัน ดังนั้นฉันจะบอกว่าแนวทางที่ดีที่สุดคือif(!Element.prototype.getParentIndex) Element.prototype.getParentIndex=Element.prototype.parentIndex?function() {return this.parentIndex}:function() {return Array.prototype.indexOf.call(this.parentNode.children, this)}
แจ็คกิฟฟิน

3
เนื่องจากในอนาคตgetParentIndex()อาจมีลายเซ็นที่แตกต่างจากการใช้งานของคุณ
mikemaccana

7

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

ฉันตั้งสมมติฐานว่าด้วยองค์ประกอบที่ลำดับลูก ๆ ทั้งหมดบนเอกสารตามลำดับวิธีที่เร็วที่สุดควรทำการค้นหาแบบไบนารีเปรียบเทียบตำแหน่งเอกสารขององค์ประกอบ อย่างไรก็ตามตามที่แนะนำไว้ในข้อสรุปสมมติฐานถูกปฏิเสธ ยิ่งคุณมีองค์ประกอบมากเท่าไหร่ก็ยิ่งมีศักยภาพในการทำงานมากขึ้นเท่านั้น ตัวอย่างเช่นหากคุณมี 256 องค์ประกอบ (อย่างเหมาะสมที่สุด) คุณจะต้องตรวจสอบเพียง 16 องค์ประกอบเท่านั้น! ราคา 65536 เพียง 256! ประสิทธิภาพเพิ่มขึ้นเป็น 2 เท่า! ดูตัวเลข / สถิติเพิ่มเติม เยี่ยมชม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);

จากนั้นวิธีที่คุณใช้คือการรับคุณสมบัติ "parentIndex" ขององค์ประกอบใด ๆ ตัวอย่างเช่นดูการสาธิตต่อไปนี้

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

ข้อ จำกัด

  • การใช้โซลูชันนี้จะไม่ทำงานใน IE8 และต่ำกว่า

การค้นหาแบบไบนารี VS เชิงเส้นใน 200,000 องค์ประกอบ (อาจทำให้เบราว์เซอร์มือถือบางตัวขัดข้องระวัง!):

  • ในการทดสอบนี้เราจะดูว่าการค้นหาเชิงเส้นใช้เวลานานเพียงใดในการค้นหาองค์ประกอบตรงกลาง VS การค้นหาแบบไบนารี ทำไมต้องเป็นธาตุกลาง? เนื่องจากอยู่ในตำแหน่งเฉลี่ยของสถานที่อื่น ๆ ทั้งหมดดังนั้นจึงแสดงตำแหน่งที่เป็นไปได้ทั้งหมดได้ดีที่สุด

การค้นหาแบบไบนารี

ย้อนกลับ (`lastIndexOf`) การค้นหาเชิงเส้น

ส่งต่อ (`indexOf`) การค้นหาเชิงเส้น

ก่อนหน้า ElementSibling Counter Search

นับจำนวน PreviousElementSiblings เพื่อรับ parentIndex

ไม่มีการค้นหา

สำหรับการเปรียบเทียบผลลัพธ์ของการทดสอบจะเป็นอย่างไรหากเบราว์เซอร์ปรับให้เหมาะสมกับการค้นหา

การถูกกระทบกระแทก

อย่างไรก็ตามหลังจากดูผลลัพธ์ใน Chrome ผลลัพธ์จะตรงกันข้ามกับที่คาดไว้ การค้นหาเชิงเส้นไปข้างหน้าโง่เง่า 187 ms, 3850% เร็วกว่าการค้นหาแบบไบนารี เห็นได้ชัดว่า Chrome ชิงไหวชิงพริบอย่างน่าอัศจรรย์console.assertและปรับให้เหมาะสมออกไปหรือ (ในแง่ดีกว่านั้น) Chrome ใช้ระบบการจัดทำดัชนีตัวเลขสำหรับ DOM ภายในและระบบการจัดทำดัชนีภายในนี้จะเปิดเผยผ่านการเพิ่มประสิทธิภาพที่ใช้กับArray.prototype.indexOfเมื่อใช้กับHTMLCollectionวัตถุ


มีประสิทธิภาพ แต่ทำไม่ได้
applemonkey496

3

ใช้อัลกอริทึมการค้นหาแบบไบนารีเพื่อปรับปรุงประสิทธิภาพเมื่อโหนดมีพี่น้องในปริมาณมาก

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

ไม่ทำงาน ฉันจำเป็นต้องชี้ให้เห็นว่าเวอร์ชัน IE และเวอร์ชัน "เบราว์เซอร์อื่น" จะคำนวณผลลัพธ์ที่แตกต่างกัน เทคนิค "เบราว์เซอร์อื่น" ทำงานได้ตามที่คาดไว้โดยได้รับตำแหน่งที่ n ภายใต้โหนดหลักอย่างไรก็ตามเทคนิค IE "ดึงข้อมูลตำแหน่งลำดับของวัตถุตามลำดับต้นทางเมื่อวัตถุปรากฏในคอลเล็กชันทั้งหมดของเอกสาร" ( msdn.microsoft .com / en-gb / library / ie / ms534635 (v = vs.85) .aspx ) เช่นฉันได้ 126 โดยใช้เทคนิค "IE" แล้วใช้อีก 4 ตัว
Christopher Bull

1

ฉันมีปัญหากับโหนดข้อความและแสดงดัชนีผิด นี่คือเวอร์ชันที่ต้องแก้ไข

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

อดีต

/* 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
คุณอธิบายได้ไหมว่าทำไมคุณถึงขยายจากElement.prototype? ฟังก์ชั่นดูมีประโยชน์ แต่ฉันไม่รู้ว่าฟังก์ชันเหล่านี้ทำหน้าที่อะไร (แม้ว่าการตั้งชื่อจะชัดเจนก็ตาม)
A1rPun

@ ขยาย Element.prototype เหตุผลคือความคล้ายคลึงกัน ... 4 เช่น elemen.children, element.parentNode ฯลฯ ... ดังนั้นวิธีเดียวกับที่คุณระบุ element.siblings .... วิธีการกลุ่มมีความซับซ้อนเล็กน้อยทำให้ฉันต้องการขยาย เล็ก ๆ น้อย ๆ วิธีการที่พี่น้องจะ elelemts เหมือนกันโดย NodeType เดียวกันและมีลักษณะเดียวกันแม้จะไม่ได้มีบรรพบุรุษเดียวกัน
bortunac

ฉันรู้ว่าการขยายต้นแบบคืออะไร แต่ฉันอยากรู้ว่าโค้ดของคุณใช้อย่างไร el.group.value()?? ความคิดเห็นแรกของฉันมีไว้เพื่อปรับปรุงคุณภาพของคำตอบของคุณ
A1rPun

วิธีการของกลุ่มและพี่น้องส่งคืน Array พร้อมองค์ประกอบ Dom .. .... ขอบคุณสำหรับความคิดเห็นและเหตุผลของความคิดเห็น
bortunac

สง่างามมาก แต่ก็ช้ามาก
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>

คุณไม่จำเป็นต้องใช้ jQuery ที่นี่
Vitaly Zdanevich
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.