子ノードのインデックスを取得する


124

まっすぐなjavascript(つまり、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.childNodesparent.children?ではなく、行う必要はありません。後者は、Elements特定のTextノードを除いて、のみをリストします...ここでの回答の一部、たとえばを使用することはpreviousSibling、すべての子ノードの使用に基づいていますが、その他はElements である子だけに悩まされています...(!)
マイクげっ歯類

@mikerodent最初にこの質問をしたとき、自分の目的が何であったか覚えていませんが、それは私が知らなかった重要な詳細です。注意しない限り、の.childNodes代わりに必ず使用してください.children。あなたが指摘したように、投稿された上位2つの回答は異なる結果をもたらします。
すべての労働者は必須

1000以上のノードで何千ものルックアップを実行することを計画している場合は、ノードに情報をアタッチします(たとえば、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
このアプローチでは、子インデックスを決定するために同じ回数の反復が必要となるため、はるかに高速になるかわかりません。
マイケル

31
1行バージョン:for (var i=0; (node=node.previousSibling); i++);
スコットマイル

2
@sfarbota Javascriptはブロックスコープを認識しないため、iアクセス可能になります。
A1rPun 2014

3
なぜなら間の違いであろう@nepdev .previousSibling.previousElementSibling。前者はテキストノードにヒットしますが、後者はヒットしません。
abluejelly

132

indexOfこれを使うのが好きになりました。のでindexOf上にあるArray.prototypeparent.childrenされNodeList、あなたが使用する必要がありますcall();それは一種醜いのだが、それは任意のJavaScriptのDEVがとにかくに精通している必要があることを1つのライナーと用途の機能です。

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

23
Fwiw、を使用[]すると、そのコードを実行するたびにArrayインスタンスが作成されます。これは、を使用するよりもメモリとGCの効率が低下しArray.prototypeます。
スコットマイル

@ScottMilesもう少し言ったことを説明してもらえますか?[]メモリ上でガベージ値としてクリーンになりませんか?
mrReiha 2015年

14
[].indexOfエンジンを評価するindexOfには、プロトタイプの実装にアクセスするためだけに配列インスタンスを作成する必要があります。インスタンス自体は未使用になります(GCを実行します。リークではなく、単にサイクルを無駄にしているだけです)。Array.prototype.indexOf匿名インスタンスを割り当てずに、その実装に直接アクセスします。違いはほとんどすべての状況で無視できるようになるので、率直に言って気にする価値がないかもしれません。
スコットマイルズ

3
IEのエラーに注意してください!Internet Explorer 6、7、および8でサポートされていましたが、コメントノードが誤って含まれています。ソース」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オブジェクトを

  • indexOfindexOfこれでArrayオブジェクトができたので申請できます。


2
最も優れたソリューション、はるかに:)
Amit Saxena

1
ただし、Chrome 45およびFirefox 32からのみ利用でき、Internet Explorerではまったく利用できません。
Peter

Internet Explorerはまだ生きていますか?Just Jock .. OK 、Internet Explorerで機能させるにはポリフィルが必要Array.from
Abdennour TOUMI 2017年

9
MDNによれば、Array.from() creates a new Array instance from an array-like or iterable object.を呼び出すだけでインデックスを見つけるために新しい配列インスタンスを作成すると、操作の頻度によってはメモリやGCの効率が悪くなる可能性があります。理想的です。
TheDarkIn1978 2017年

1
@ TheDarkIn1978私は👍🏻コードエレガンス&アプリのパフォーマンスとのトレードオフがある承知しています
Abdennour TOUMI

38

ES-より短い

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

スプレッドオペレーターはそのショートカットです


それは興味深い演算子です。
すべての労働者は必須

違いは何だe.parentElement.childNodesとはe.parentNode.children
mesqueeb 2018年

4
childNodes同様にテキストノードが含まれて
フィリップ・

Type 'NodeListOf<ChildNode>' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)
Typescriptを使用すると、ZenVentziは

9

(安全のために接頭辞が付けられた)element.getParentIndex()を追加します。

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

1
ウェブ開発の苦痛の理由があります:prefix-jumpy dev's。なぜしないのです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)}
ジャック・ギフィン2017

3
将来getParentIndex()は実装とは異なるシグネチャを持つ可能性があるためです。
mikemaccana 2017

7

すべての子がドキュメント上で順番に並べられている要素がある場合、最速の方法は、要素のドキュメントの位置を比較してバイナリ検索を行うことです。ただし、結論で紹介したように、仮説は拒否されます。要素が多いほど、パフォーマンスの可能性が高くなります。たとえば、256個の要素がある場合、(最適に)そのうち16個だけをチェックするだけで済みます。65536の場合、256のみです。パフォーマンスは2の累乗に成長します!その他の数値/統計をご覧ください。ウィキペディアにアクセス

(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以下では機能しません。

20万個の要素に対するバイナリVS線形検索(一部のモバイルブラウザーをクラッシュさせる可能性があります。注意してください):

  • このテストでは、線形検索が中間要素とバイナリ検索を見つけるのにかかる時間を確認します。なぜ真ん中の要素?他のすべての場所の平均的な場所にあるため、可能な場所すべてを最もよく表すからです。

バイナリ検索

後方( `lastIndexOf`)線形検索

フォワード( `indexOf`)線形検索

PreviousElementSiblingカウンター検索

parentIndexを取得するためにPreviousElementSiblingsの数をカウントします。

検索なし

ブラウザが検索を最適化した場合のテスト結果のベンチマーク。

脳震盪

ただし、結果をChromeで表示すると、期待した結果とは逆になります。ダンバーフォワードリニア検索は、バイナリ検索よりも高速な187ミリ秒、3850%でした。明らかに、Chromeはどういうわけかを魔法のように上回り、console.assert最適化しました。または(より楽観的に)ChromeはDOMの数値インデックスシステムを内部で使用しており、この内部インデックスシステムはArray.prototype.indexOfHTMLCollectionオブジェクトでの使用時に適用される最適化を通じて公開されます。


効率的ですが実用的ではありません。
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を取得しました。
クリストファーブル

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をアドレス指定するのと同じ方法....グループメソッドは、拡張したい少し複雑な原因です同じnodeTypeで同じ属性を持ち、同じ祖先がなくても同じ属性を持つ要素への兄弟アプローチ
bortunac

拡張プロトタイプが何であるかは知っていますが、コードがどのように使用されるのか知りたいです。el.group.value()??。私の最初のコメントはあなたの答えの質を改善するためにあります。
A1rPun

groupおよびsiblingsメソッドは、作成されたdom要素を持つ配列を返します.. ....コメントとコメントの理由に感謝
bortunac

とてもエレガントですが、とても遅いです。
ジャックギフィン2017


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