자식 노드 인덱스 가져 오기


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
미안해, 내가 완전 멍청이야? 여기에는 겉보기에 배운 답이 많이 있지만 모든 자식 노드를 얻으려면 ? parent.childNodes대신 할 필요가 없습니다 parent.children. 후자 Elements는 특정 Text노드를 제외하고 만 나열합니다 . 여기에있는 일부 답변 (예 : using previousSibling)은 모든 자식 노드를 사용하는 것에 기반을두고있는 반면 다른 것들은 Elements ... (!)
mike rodent

@mikerodent 처음에이 질문을했을 때 내 목적이 무엇이 었는지 기억이 나지 않지만 그것은 내가 알지 못했던 주요 세부 사항입니다. 당신이 조심 아니라면, .childNodes분명히 대신 사용되어야한다 .children. 게시 된 상위 2 개 답변은 지적한대로 다른 결과를 제공합니다.
모든 노동자는 필수적이다

1000 개 이상의 노드를 통해 수천 개의 조회를 계획 할 때 노드에 정보를 첨부하십시오 (예 : child.dataset를 통해). 목표는 O (n) 또는 O (n ^ 2) 알고리즘을 O (1) 알고리즘으로 변환하는 것입니다. 단점은 노드가 정기적으로 추가 및 제거되면 노드에 연결된 관련 위치 정보도 업데이트해야하므로 성능이 향상되지 않을 수 있습니다. 가끔 반복하는 것은 큰 문제는 아니지만 (예 : 클릭 핸들러) 반복되는 반복은 문제가됩니다 (예 : mousemove).
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 2014 년

3
@nepdev 그 때문에 차이점이 될 것입니다 .previousSibling.previousElementSibling. 전자는 텍스트 노드를 적중하고 후자는 그렇지 않습니다.
abluejelly

132

나는 이것을 사용 indexOf하는 것을 좋아하게되었습니다 . 때문에 indexOfArray.prototypeparent.children이다 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 index = [] .indexOf.call (child.parentNode.children, child);
cuixiping

23
Fwiw, using []은 해당 코드를 실행할 때마다 Array 인스턴스 를 생성하므로 Array.prototype.
Scott Miles

@ScottMiles 당신이 말한 것을 조금 더 설명해달라고 부탁해도 될까요? []가비지 값으로 메모리를 정리 하지 않습니까?
mrReiha

14
[].indexOf엔진 을 평가하려면 indexOf프로토 타입 의 구현에 액세스하기 위해 배열 인스턴스를 만들어야합니다 . 인스턴스 자체는 사용되지 않습니다 (GC를 수행하고 누출이 아니라주기를 낭비하는 것입니다). Array.prototype.indexOf익명 인스턴스를 할당하지 않고 해당 구현에 직접 액세스합니다. 그 차이는 거의 모든 상황에서 미미할 것이므로 솔직히 신경 쓸 가치가 없을 수도 있습니다.
Scott Miles

3
IE의 오류에주의하십시오! Internet Explorer 6, 7 및 8이이를 지원했지만 주석 노드가 잘못 포함되었습니다. Source " developer.mozilla.org/en-US/docs/Web/API/ParentNode/…
Luckylooke

101

ES6 :

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

설명 :

  • element.parentNode.childrenelement해당 요소를 포함하여 의 형제를 반환합니다 .

  • Array.from→이의 생성자를 캐스트 childrenArray객체

  • indexOf→ 이제 개체 indexOf가 있으므로 신청할 수 있습니다 Array.


2
:) 지금까지 대부분의 우아한 솔루션
아 미트 Saxena는

1
그러나 Chrome 45 및 Firefox 32에서만 사용할 수 있으며 Internet Explorer에서는 전혀 사용할 수 없습니다.
피터

Internet Explorer가 아직 살아 있습니까? 그냥 조크는 .. 좋아 당신이 필요하므로 polyfill을 만들기 위해 Array.from인터넷 익스플로러에서 작동
Abdennour TOUMI

9
MDN에 따르면 Array.from ()을 호출 creates a new Array instance from an array-like or iterable object.하면 인덱스를 찾기 위해 새 배열 인스턴스를 만드는 것이 메모리 또는 GC 효율적이지 않을 수 있습니다. 이상.
TheDarkIn1978

1
@ TheDarkIn1978 코드 우아함과 앱 성능 사이에 절충안이 있다는 것을 알고 있습니다 👍🏻
Abdennour TOUMI

38

ES— 짧게

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

스프레드 연산자는이를위한 지름길입니다.


흥미로운 연산자입니다.
모든 근로자가 필수입니다.

e.parentElement.childNodes과 의 차이점은 무엇입니까 e.parentNode.children?
mesqueeb

4
childNodes뿐만 아니라 텍스트 노드를 포함
필립

타이프하면 얻을 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 */ }? 어쨌든 이것이 앞으로 표준에 구현되면 .NET과 같은 게터로 구현 될 것 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)}
잭 Giffin

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 선형 검색 20 만 요소에서 (일부 모바일 브라우저가 충돌 할 수 있습니다.주의!) :

  • 이 테스트에서는 선형 검색이 중간 요소와 이진 검색을 찾는 데 걸리는 시간을 확인합니다. 왜 중간 요소입니까? 다른 모든 위치의 평균 위치에 있기 때문에 가능한 모든 위치를 가장 잘 나타냅니다.

이진 검색

역방향 (`lastIndexOf`) 선형 검색

앞으로 (`indexOf`) 선형 검색

PreviousElementSibling 카운터 검색

parentIndex를 가져 오기 위해 PreviousElementSiblings의 수를 계산합니다.

검색 안함

브라우저가 검색을 최적화하면 테스트 결과가 어떻게 될지 벤치마킹하기 위해.

Conculsion

그러나 Chrome에서 결과를 본 후 결과는 예상했던 것과 반대입니다. Dumber forwards 선형 검색은 이진 검색보다 빠른 187ms (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 ). 예를 들어 "IE"기술을 사용하여 126 개를 얻었고 나머지 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 2015

@ extend Element.prototype 이유는 유사성입니다 ... 4 ex elemen.children, element.parentNode 등 ... 그래서 element.siblings를 다루는 것과 같은 방식으로 .... group 메서드는 확장하고 싶은 약간 복잡한 원인입니다 조금 같은 nodeType에 의해 모두와 같은 조상을 가진조차 동일한 속성을 가진 elelemts에 형제 접근
bortunac

프로토 타입 확장이 무엇인지 알고 있지만 코드가 어떻게 사용되는지 알고 싶습니다. el.group.value()??. 내 첫 번째 의견은 답변의 질을 높이기 위해 있습니다.
A1rPun 2015

귀하의 의견과 의견의 이유 설립 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.