1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ループ内で配列長を変える配列操作

1
Posted at

ループ内で配列長を変える危険な配列操作

配列["a", "a", "b", "c"]から"a"を除いて配列["b", "c"]にしたいとする。

配列のsplice

var ary = ["a", "a", "b", "c"];
for (var i = 0; i < ary.length; i++) {
    console.log(ary[i]); // a, b, cを表示する。
    if (ary[i] === "a") ary.splice(i, 1); //aryを["a","b","c"]に変更し2番目の"a"を飛ばす。
}

配列に対してループしているのにspliceが配列の長さを4から3に変えている。
こういうプログラムは予想も付かない不具合を起こす可能性があり危険。

getElementsByTagNameなどに対するremove

<ul>
    <li>a</li>
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ul>
<script>
    var ary = document.getElementsByTagName("li");
    for (var elem of ary) {
        console.log(elem); /* <li>a</li>, <li>b</li>, <li>c</li>を表示する。 */
        if (elem.textContent === "a") elem.remove(); /* aryをHTMLCollection [<li>a</li>, <li>b</li>, <li>c</li>]に変更し2番目の"a"を飛ばす。 */
    }
</script>

elment.childNodes、element.children、getElementsByClassName、getElementsByTagNameが返す配列に対してループを回すことは危険。
ループ内で配列長を変えるという問題の構造に気づきにくい。

ループ内で使用しても安全な配列操作

querySelectorAllに対するremove

<ul>
    <li class="q_selector">a</li>
    <li class="q_selector">a</li>
    <li class="q_selector">b</li>
    <li class="q_selector">c</li>
</ul>
<script>
    var ary = document.querySelectorAll(".q_selector");
    for (var elem of ary) {
        console.log(elem); /* <li>a​</li>, <li>​a​</li>, <li>​b</li>, <li>c</li>を表示する。*/
        if (elem.textContent === "a") elem.remove(); //aryはNodeList [li, li, li, li]のまま変更されない。
    }
</script>

配列をループ内で使用しても安全な配列に変えることで安全に配列を操作できる。
この配列はremoveしても配列の長さが変わらない。

jQueryのremove

<ul>
    <li class="jq_each">a</li>
    <li class="jq_each">a</li>
    <li class="jq_each">b</li>
    <li class="jq_each">c</li>
</ul>
<script>
    var ary = $(".jq_each");
    ary.each(function() {
        console.log(this); /* <li>​a​</li>, <li>​a​</li>, <li>​b</li>, <li>c</li>を表示する。 */
        if ($(this).text() === "a") $(this).remove(); //aryはk.fn.init [li, li, li, li, prevObject]のまま変更されない。
    });
</script>

配列をループ内で使用しても安全な配列に変えることで安全に配列を操作できる。
この配列もremoveしても配列の長さが変わらない。

配列のdelete

var ary = ["a", "a", "b", "c"];
for (var i = 0; i < ary.length; i++) {
    console.log(ary[i]); // a, a, b, cを表示する。
    if (ary[i] === "a") delete ary[i]; // aryを[empty, empty, "b", "c"]に変更する。
}

配列操作をループ内で使用しても安全な配列操作に変えることで安全に配列を操作できる。
deleteは配列の長さを変えない。

spliceの後でループを戻す

var ary = ["a", "a", "b", "c"];
for (var i = 0; i < ary.length; i++) {
    console.log(ary[i]); // a, a, b, cを表示する。
    if (ary[i] === "a") {
        ary.splice(i, 1); // aryを["b", "c"]に変更する。
        i = i - 1; //spliceの後でループを1つ戻す。
    }
}

配列のspliceはループの順番を戻すことで正常に配列を操作できる。
配列の長さが変わるのでこのやり方は注意が必要。

配列の削除問題

この問題を密かに配列の削除問題と呼んでいるが、配列の削除問題と一言で説明しても問題の構造が伝わらないという問題がある。後者の問題を解決するためにこの記事を書いた。

配列の削除問題は知られていないが、このような問題はQiitaやteratailで見つけられる。

1
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?