innerHTML += で一覧を作ると、遅くなるうえに入力した文字まで消える
JavaScript で一覧を作るとき、ループの中で ul.innerHTML += "<li>...</li>" と1行ずつ足していく書き方があります。短く書けて分かりやすいのですが、件数が増えると急に重くなります。さらに、速さとは別に、入力欄の文字やボタンの動きが消えるという落とし穴もあります。
この記事では、ブラウザで実際に測った結果をもとに、何が起きているのかと、代わりの書き方を整理します。
計測は macOS の WebKit(Safari と同じエンジン)で、各5回の中央値です。このブラウザの時間の計測は1ミリ秒単位なので、1ミリ秒より短い処理は、ほとんど「0ms」と表示されます。測ったのは JavaScript で要素を作り終えるまでの時間で、画面に描かれるまでの時間は含みません。数値は環境によって変わるので、比べる目安として見てください。
測った書き方
// A: ループの中で innerHTML に足していく
for (let i = 0; i < N; i++) {
ul.innerHTML += `<li>商品${i}</li>`;
}
// B: 文字列を作ってから、最後に1回だけ入れる
let html = "";
for (let i = 0; i < N; i++) {
html += `<li>商品${i}</li>`;
}
ul.innerHTML = html;
// C: 要素を作って追加する
for (let i = 0; i < N; i++) {
const li = document.createElement("li");
li.textContent = `商品${i}`;
ul.appendChild(li);
}
結果
- 100件: A は約1ms
- 300件: A は約8ms
- 1,000件: A は約90ms、B と C は 0ms 表示(1ms 未満)
- 3,000件: A は約765ms、B は 0ms 表示(1ms 未満)、C は約1ms
1,000件から3,000件へ件数を3倍にすると、A の時間は約8.5倍になりました。300件から1,000件(約3.3倍)でも約11倍です。件数に比例して増えるのではなく、「件数×件数」に近い増え方をしています(件数を3倍にすると、時間は約9倍)。B と C は、3,000件でも要素を作り終えるまでの時間はほとんどかかりませんでした。
なぜ A だけ遅いのか
ul.innerHTML += "<li>...</li>" は、見た目は「1行足す」ですが、実際にはこう動きます。
- 今の中身を全部、文字列として取り出す
- そこに新しい1行をくっつける
- くっつけた文字列全体から、中の要素を全部作り直す
つまり、1件足すたびに、それまでの全部を作り直しています。100件目を足すときは100件分、1,000件目を足すときは1,000件分を作り直すので、件数が増えるほど1回あたりの手間も増えていきます。
速さより困ること:入力やボタンが消える
作り直しは、遅いだけでなく、中にあったものを消してしまいます。実際に試すと、こうなりました。
innerHTML +=した要素の中にある入力欄に文字を打ってから、何かを足すと、入力していた文字が空になった(入力前の状態に戻る)- 同じ要素の中のボタンに付けていたクリックの処理(
addEventListener)が、足したあとは動かなくなった
見た目は同じボタンでも、中では新しく作り直された別のボタンになっているためです。「件数が少ないから速さは気にしなくていい」場面でも、この落とし穴は件数に関係なく起きます。
代わりの書き方
- まとめて表示するなら → B のように文字列を作ってから、最後に1回だけ入れる(ただし B も中身を丸ごと入れ替えるので、すでに入力欄やボタンが入っている一覧に使うと、A と同じく入力やクリックの処理が消えます)
- すでにある中身を残したまま足すなら → C のように
createElementで要素を作ってappendChildで足す(入力やクリックの処理は消えません)
A と B は、入れた文字を HTML として読みます。商品名のように自分で用意した文字なら問題ありませんが、利用者が入力した文字をそのまま入れると、タグとして解釈されて意図しない表示や動作につながることがあります。利用者の入力を表示するときは、文字をそのまま文字として入れる C の textContent を使ってください。
AI が書いたコードを見るときは
AI が書いたコードにも、innerHTML += の形が混ざっていることがあります。少ない件数で試すと問題なく見えるので、innerHTML += がないか一度見てみてください。ループの中にあれば遅くなる原因になり、ループの外の1回だけでも、中に入力欄やボタンがあれば入力が消える原因になります。
面談なしで、今日から始められます
この講座は 未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまで を全24セッションで体系化した教材付きプランです。無料相談を挟まず、申し込んだその日から教材で学習を始められます。AIが学習パートナーになって何度でも質問でき、つまずいた所だけチャットで直接サポートします。
- 今日から始める(教材完全版+月5,500円・チャット質問し放題・いつでも解約OK)→ https://menta.work/plan/20251?ref=menta-knowledge
- いきなりは不安な方へ:無料の教材体験版もあります(最初の数セッション分)→ プラン詳細をご覧ください

