ループ内で配列長を変える危険な配列操作
配列["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で見つけられる。