SOFTELメモ Developer's blog

会社概要 ブログ 調査依頼 採用情報 ...
技術者募集中

【Javascript】要素の子要素削除の処理(セレクトボックス初期化など)

例えば、ul,li要素のリストや、selectの中のoption要素を、追加したり削除したりする処理を考えます。

<select name=”staff”>
<option value=””> – </option>
<option value=”1″>いち</option>
<option value=”2″>に</option>
<option value=”3″>さん</option>
<option value=”4″>よん</option>
<option value=”5″>ご</option>
</select>

今、上のセレクトボックスから、選択状態のoption以外を、全部取り除こうとしています。
さて、間違い探し!

var select = document.form[0].staff
var options = select.options
for (var i = 0, l = options.length; i < l; ++i) {
	if(!options[i].selected) {
		select.removeChild(options[i]);
	}
}

これは期待したとおりに動きません。
options.length は 6 でスタートします。6回ループする予定です。
removeChild(options[0])、removeChild(options[1])、removeChild(options[2])……しようと思いますが、
removeChild(options[0])した時点で、残りが、optionsの1,2,3,4,5ではなくて、0,1,2,3,4になります。
先頭0番を削除したら、新しい先頭0番が現れるわけです。その状態で次の1番を削除しようとすると……
次のループでoptions[1]をremoveChildすると、options[0]はそのまま残ります。
次のループでoptions[2]をremoveChildすると、options[0]と新しいoptions[1]はそのまま残ります。
結局1つずつ飛び飛びにremoveChildして終わります。

この場合は、末尾から処理していけば、期待した動きをします。

var select = document.form[0].staff
var options = select.options
for (var i = options.length - 1; 0 <= i; --i) {
	if(!is[i].selected) {
		select.removeChild(is[i]);
	}
}

もしくは、「DOM走査中にDOM操作をしてはならない」というルールを自分に課すなら、
ループしながら、削除する予定の要素を配列にでもメモっておき、
次に改めて、メモに書かれた要素たちを削除するというのが確実です。

<おまけ>
ある要素の子要素を全部削除するつもりならこれが簡単。
子ノードがある限り、すべてremoveChild()。先頭に来た要素(firstChild)を順次取っていく。

function removeChildren(x)
{
	if (x.hasChildNodes()) {
		while (x.childNodes.length > 0) {
			x.removeChild(x.firstChild)
		}
	}
}

関連するメモ

コメント(2)

助かりました 2013年12月11日 23:09

javascriptの初心者です。
まんまと、ご指摘の「一つ飛びに削除する」という不具合に悩んでいました。
なるほど、おっしゃる通りに直したら動きました!
ありがとうございました。

yoshimura 2013年12月11日 23:16

コメントありがとうございます。お役に立てて何よりです ^-^