Nhận chỉ mục nút con


125

Trong javascript đơn giản (tức là không có phần mở rộng như jQuery, v.v.), có cách nào để xác định chỉ mục của nút con bên trong nút cha của nó mà không cần lặp lại và so sánh tất cả các nút con không?

Ví dụ,

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ó cách nào tốt hơn để xác định chỉ số của trẻ không?


2
Xin lỗi, tôi có phải là người hoàn toàn không? Có rất nhiều câu trả lời dường như đã học ở đây, nhưng để có được tất cả các nút con, bạn không cần phải làm gì parent.childNodes, đúng hơn là parent.children?. Phần sau chỉ liệt kê Elements, loại trừ Textcác nút cụ thể ... Một số câu trả lời ở đây, ví dụ như cách sử dụng previousSibling, dựa trên việc sử dụng tất cả các nút con, trong khi những người khác chỉ bận tâm với các nút con là Element... (!)
mike gặm nhấm

@mikerodent Tôi không nhớ mục đích của mình khi ban đầu hỏi câu hỏi này là gì, nhưng đó là một chi tiết quan trọng mà tôi không biết. Trừ khi bạn cẩn thận, .childNodeschắc chắn nên được sử dụng thay vì .children. 2 câu trả lời được đăng hàng đầu sẽ cho kết quả khác nhau như bạn đã chỉ ra.
Tất cả người lao động đều cần thiết.

Khi lập kế hoạch thực hiện hàng nghìn tìm kiếm trên hơn 1000 nút, sau đó đính kèm thông tin vào nút (ví dụ: qua child.dataset). Mục tiêu là chuyển đổi thuật toán O (n) hoặc O (n ^ 2) thành thuật toán O (1). Nhược điểm là nếu các nút được thêm và xóa thường xuyên, thông tin vị trí liên quan được gắn với các nút cũng sẽ phải được cập nhật, điều này có thể không dẫn đến bất kỳ lợi ích nào về hiệu suất. Việc lặp đi lặp lại không thường xuyên không phải là vấn đề lớn (ví dụ: trình xử lý nhấp chuột) nhưng lặp đi lặp lại là vấn đề (ví dụ: di chuột).
CubicleSoft

Câu trả lời:


122

bạn có thể sử dụng thuộc previousSiblingtính để lặp lại qua các anh chị em cho đến khi bạn quay lại nullvà đếm số anh chị em đã gặp:

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

Xin lưu ý rằng trong các ngôn ngữ như Java, có một getPreviousSibling()hàm, tuy nhiên trong JS thì hàm này đã trở thành một thuộc tính - previousSibling.


2
Vâng. Mặc dù vậy, bạn đã để lại getPreviousSibling () trong văn bản.
Tim Down

7
cách tiếp cận này yêu cầu cùng một số lần lặp để xác định chỉ mục con, vì vậy tôi không thể thấy nó sẽ nhanh hơn nhiều như thế nào.
Michael

31
Phiên bản một dòng:for (var i=0; (node=node.previousSibling); i++);
Scott Miles

2
@sfarbota Javascript không biết phạm vi khối, vì vậy isẽ có thể truy cập được.
A1rPun

3
@nepdev Đó sẽ là do sự khác biệt giữa .previousSibling.previousElementSibling. Cái trước truy cập vào các nút văn bản, cái sau thì không.
abluejelly

132

Tôi đã trở nên thích sử dụng indexOfcho điều này. Bởi vì indexOflà bật Array.prototypeparent.childrenlà một NodeList, bạn phải sử dụng call();Nó hơi xấu nhưng nó là một lớp lót và sử dụng các chức năng mà bất kỳ nhà phát triển javascript nào cũng phải quen thuộc.

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, con);
cuixiping

23
Fwiw, sử dụng []tạo ra một cá thể Mảng mỗi khi bạn chạy mã đó, điều này kém hiệu quả hơn đối với bộ nhớ và GC so với việc sử dụng Array.prototype.
Scott Miles

@ScottMiles Tôi có thể yêu cầu giải thích những gì bạn đã nói thêm một chút không? Không []được dọn dẹp trên bộ nhớ như một giá trị rác?
mrReiha

14
Để đánh giá [].indexOfcông cụ phải tạo một cá thể mảng chỉ để truy cập việc indexOftriển khai trên nguyên mẫu. Bản thân cá thể không được sử dụng (nó có GC, nó không phải là một rò rỉ, nó chỉ lãng phí các chu kỳ). Array.prototype.indexOftruy cập trực tiếp việc triển khai đó mà không cần phân bổ một phiên bản ẩn danh. Sự khác biệt sẽ không đáng kể trong hầu hết mọi trường hợp, vì vậy thẳng thắn mà nói, nó có thể không đáng quan tâm.
Scott Miles

3
Cẩn thận với lỗi trong IE! Internet Explorer 6, 7 và 8 đã hỗ trợ nó, nhưng bao gồm sai các nút Nhận xét. Nguồn " developer.mozilla.org/en-US/docs/Web/API/ParentNode/…
Luckylooke

101

ES6:

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

Giải trình :

  • element.parentNode.children→ Trả về anh em của element, bao gồm cả phần tử đó.

  • Array.from→ Truyền hàm tạo của childrenmột Arrayđối tượng

  • indexOf→ Bạn có thể đăng ký indexOfvì bây giờ bạn đã có một Arrayđối tượng.


2
Giải pháp thanh lịch nhất, cho đến nay :)
Amit Saxena

1
Nhưng chỉ khả dụng trên Chrome 45 và Firefox 32 và hoàn toàn không khả dụng trên Internet Explorer.
Peter

Internet Explorer vẫn tồn tại? Just Jock .. Ok vì vậy bạn cần một polyfill để tạo ra Array.fromcác tác phẩm trên Internet explorer
Abdennour TOUMI

9
Theo MDN, việc gọi Array.from () creates a new Array instance from an array-like or iterable object. Việc tạo một cá thể mảng mới chỉ để tìm một chỉ mục có thể không hiệu quả về bộ nhớ hoặc GC, tùy thuộc vào mức độ thường xuyên của hoạt động, trong trường hợp đó, việc lặp lại, như được giải thích trong câu trả lời được chấp nhận, sẽ nhiều hơn lý tưởng.
TheDarkIn1978

1
@ TheDarkIn1978 Tôi biết có một nghề-off giữa mã sang trọng & hiệu suất ứng dụng 👍🏻
Abdennour Toumi

38

ES — Ngắn hơn

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

Toán tử spread là một phím tắt cho điều đó


Đó là một toán tử thú vị.
Tất cả Công nhân đều cần thiết vào

Sự khác biệt giữa e.parentElement.childNodesvà là e.parentNode.childrengì?
mesqueeb

4
childNodesbao gồm cả các nút văn bản
philippe

Với nguyên cảo bạn nhận được Type 'NodeListOf<ChildNode>' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)
ZenVentzi

9

Thêm một element.getParentIndex () (tiền tố cho an toàn):

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

1
Lý do cho sự đau khổ của phát triển web: tiền tố-jumpy dev's. Tại sao không chỉ làm if (!Element.prototype.getParentIndex) Element.prototype.getParentIndex = function(){ /* code here */ }? Dù sao đi nữa, nếu điều này từng được triển khai thành tiêu chuẩn trong tương lai thì nó có khả năng sẽ được thực hiện như một sự thay đổi element.parentIndex. Vì vậy, tôi sẽ nói là cách tiếp cận tốt nhất làif(!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
Vì tương lai getParentIndex()có thể có chữ ký khác với việc bạn triển khai.
mikemaccana

7

Tôi giả thuyết rằng cho một phần tử mà tất cả các phần tử con của nó được sắp xếp theo thứ tự trên tài liệu, thì cách nhanh nhất là thực hiện tìm kiếm nhị phân, so sánh vị trí tài liệu của các phần tử. Tuy nhiên, như đã giới thiệu trong phần kết luận, giả thuyết đã bị bác bỏ. Bạn càng có nhiều yếu tố, tiềm năng hiệu suất càng lớn. Ví dụ: nếu bạn có 256 phần tử, thì (tối ưu) bạn sẽ chỉ cần kiểm tra 16 phần tử trong số đó! Đối với 65536, chỉ 256! Hiệu suất tăng lên đến sức mạnh của 2! Xem thêm các con số / thống kê. Truy cập 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);

Sau đó, cách bạn sử dụng nó là lấy thuộc tính 'parentIndex' của bất kỳ phần tử nào. Ví dụ, hãy xem bản demo sau.

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

Hạn chế

  • Việc triển khai giải pháp này sẽ không hoạt động trong IE8 trở xuống.

Tìm kiếm nhị phân VS tuyến tính Trên 200 nghìn phần tử (có thể làm hỏng một số trình duyệt di động, HÃY CẨN THẬN!):

  • Trong thử nghiệm này, chúng ta sẽ xem một tìm kiếm tuyến tính mất bao lâu để tìm phần tử ở giữa VS một tìm kiếm nhị phân. Tại sao lại là phần tử ở giữa? Bởi vì nó nằm ở vị trí trung bình của tất cả các vị trí khác, vì vậy nó thể hiện tốt nhất tất cả các vị trí có thể có.

Tìm kiếm nhị phân

Backwards (`lastIndexOf`) Tìm kiếm tuyến tính

Chuyển tiếp (`indexOf`) Tìm kiếm tuyến tính

TrướcElementSibling Counter Search

Đếm số PreviousElementSiblings để có được parentIndex.

Không tìm kiếm

Để đo điểm chuẩn, kết quả của bài kiểm tra sẽ như thế nào nếu trình duyệt tối ưu hóa tìm kiếm.

Cuộc thảo luận

Tuy nhiên, sau khi xem kết quả trên Chrome, kết quả lại trái ngược với những gì mong đợi. Tìm kiếm tuyến tính chuyển tiếp dumber là 187 ms đáng ngạc nhiên, 3850%, nhanh hơn so với tìm kiếm nhị phân. Rõ ràng là bằng cách nào đó, Chrome đã vượt trội một cách kỳ diệu console.assertvà tối ưu hóa nó, hoặc (lạc quan hơn) Chrome sử dụng nội bộ hệ thống lập chỉ mục số cho DOM và hệ thống lập chỉ mục nội bộ này được hiển thị thông qua các tối ưu hóa được áp dụng Array.prototype.indexOfkhi được sử dụng trên một HTMLCollectionđối tượng.


Hiệu quả, nhưng không thực tế.
applemonkey496

3

Sử dụng thuật toán tìm kiếm nhị phân để cải thiện hiệu suất khi nút có số lượng lớn anh chị em.

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

Không hoạt động. Tôi buộc phải chỉ ra rằng phiên bản IE và phiên bản "trình duyệt khác" sẽ tính toán các kết quả khác nhau. Kỹ thuật "trình duyệt khác" hoạt động như mong đợi, lấy vị trí thứ n dưới nút cha, tuy nhiên kỹ thuật IE "Lấy vị trí thứ tự của đối tượng, theo thứ tự nguồn, khi đối tượng xuất hiện trong tất cả bộ sưu tập của tài liệu" ( msdn.microsoft .com / en-gb / library / ie / ms534635 (v = vs.85) .aspx ). Ví dụ: tôi nhận được 126 bằng cách sử dụng kỹ thuật "IE", và sau đó là 4 bằng cách khác.
Christopher Bull

1

Tôi gặp sự cố với các nút văn bản và nó đang hiển thị chỉ mục sai. Đây là phiên bản để sửa chữa nó.

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

Ví dụ

/* 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
Bạn có thể giải thích lý do tại sao bạn mở rộng từ Element.prototype? Các chức năng trông hữu ích nhưng tôi không biết những chức năng này làm gì (ngay cả khi tên rõ ràng).
A1rPun

@ extension Element.prototype lý do là sự giống nhau ... 4 ex elemen.children, element.parentNode, v.v ... vì vậy cách bạn xử lý element.siblings cũng giống như vậy .... phương thức nhóm hơi phức tạp vì tôi muốn mở rộng một chút cách tiếp cận anh chị em để elelemts như nhau bởi nodeType giống nhau và có cùng một thuộc tính thậm chí không có cùng một tổ tiên
bortunac

Tôi biết mở rộng nguyên mẫu là gì nhưng tôi muốn biết mã của bạn được sử dụng như thế nào. el.group.value()?? Nhận xét đầu tiên của tôi là ở đó để cải thiện chất lượng câu trả lời của bạn.
A1rPun

phương thức nhóm và anh chị em trả về Mảng với các phần tử dom được thành lập .. .... cảm ơn bạn đã nhận xét và vì lý do nhận xét
bortunac

Rất thanh lịch, nhưng cũng rất chậm.
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>

Bạn không cần jQuery ở đây.
Vitaly Zdanevich
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.