자식 노드 인덱스 가져 오기


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하는 것을 좋아하게되었습니다 . 때문에 indexOf에 Array.prototype와 parent.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.children→ element해당 요소를 포함하여 의 형제를 반환합니다 .

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

  • 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.