スポンサーリンク

─ JavaScript で thead/tbody を動的に生成する際の落とし穴と対策

Webアプリで株価データをテーブル表示する機能を作っていたとき、不可解なバグに遭遇しました。

❗発生した問題

テーブル描画後、行の並びが次のように“おかしい”状態に:

1行目:株価データ(本来はここにヘッダーが来るべき)
2行目:ヘッダー行
3行目:株価データ(本来はヘッダーの直下に来るべき)

データとヘッダーが入れ替わってしまっているわけです。

HTML側の構造は正しいし、JS の描画処理も明らかな誤りはない。
にもかかわらず、ヘッダーがデータの間に割り込むという不可解な現象。

この記事では、
「この現象がなぜ起こるのか」
「どう直すか」
を体系的にまとめます。

🎯 結論(最初に答え)

この問題の根本原因は:

table 内に残った「空白テキストノード」が DOM の先頭行扱いになり、thead/tbody が後ろに回されてしまうため

そしてそれを引き起こす要因は2つ:

  1. HTML に元からある <thead><tbody> を JS が再生成している
  2. table の削除処理で空白ノードが残り、ブラウザの自動補完で “tbody の中身” として扱われる

結果として、描画順序が
空白 → thead → tbody
の順に並び、ヘッダーが中間に来てしまいます。

🧠 何が起きていたのか?処理フローで徹底解説

以下、ブラウザと JavaScript の動きを時系列で追っていきます。

① HTML 初期状態

HTML はこんな感じでした:

<table id="analysis-table">
  <thead></thead>
  <tbody></tbody>
</table>

一見問題なさそうですが…

スポンサーリンク

② ブラウザの table 自動補完が発動

ブラウザは table をパースするときに以下を行います:

  • table 内の改行や空白を #text ノードとして扱う
  • 不足している tbody を 自動生成 する
  • テキストノードを tbody に押し込むことがある

すると DOM はこうなる可能性があります:

<table>
  #text ← table直下にある空白
  <thead></thead>
  <tbody>#text(空白)</tbody>
</table>

この「空白ノード (#text)」が厄介者です。

③ JS の描画処理が thead/tbody を削除

if (table.tHead) table.tHead.remove();
while (table.tBodies.length > 0) table.tBodies[0].remove();

この結果:

<table>
  #text ← これだけ残る
</table>

空白ノードは削除されないため、そのまま table の先頭行扱いとなる。

④ JS が新しい thead / tbody を作成

table.createTHead();
table.createTBody();

ブラウザは残った #text の後ろに thead と tbody を追加します:

<table>
  #text  ← 1行目扱い
  <thead>…</thead> ← 2行目
  <tbody>…</tbody> ← 3行目
</table>

ここで 表示順序が狂う現象が確定 します。

⑤ tbody にデータ行を追加すると…

結果:

1行目:#text(ブラウザが「データっぽい1行目」と扱う)
2行目:ヘッダー
3行目:データ行(本来2行目になるべき)

🧩 なぜ解決が難しいのか?

この問題は通常のデバッグでは気づけません。

  • 「空白ノード」は目に見えない
  • table はブラウザが自動補完するため、HTML と DOM が完全に一致しない
  • thead/tbody の生成・削除が DOM の順序を狂わせる

ブラウザ依存の隠れた仕様が絡むため、意図しない順序の入れ替わりが起こるわけです。

🛠️ 解決策(最も確実)

✅ 対策1:HTML から thead / tbody を削除してゼロから描画する(推奨)

<table id="analysis-table"></table>

JavaScript 側で完全に生成するパターンがもっとも安全です。

✅ 対策2:renderTable() の先頭で table.innerHTML を丸ごとクリアする

function renderTable() {
    table.innerHTML = "";  // ←ここが重要
    const thead = table.createTHead();
    const tbody = table.createTBody();
    ...
}

空白ノードを含む すべての DOM を確実に消せる ので安全。

📝 教訓:table 要素は「ブラウザの自動補完」と相性が悪い

今回のバグから得られた教訓は次の通り:

1. table に初期の thead/tbody を書かないほうが安全

動的生成する場合は table の中身は空 にしておくのが鉄則。

2. innerHTML = "" は意外と重要

削除系 API(remove(), removeChild())では 空白ノードが残る

3. ブラウザは table を「勝手に修正」する

HTML と実際の DOM は必ずしも一致しません。

🎉 まとめ

今回の問題の正体は:

JS が table 内の thead/tbody を削除した際に、ブラウザが補完した空白ノードが残り、
そのノードが “1行目” として扱われた結果、ヘッダーがデータの間に入り込んだ

という、ブラウザ仕様 × DOM 操作の複合的な副作用と言えそうです。

動的にテーブルを描画する場合は、

  • HTML には空の table を置く
  • innerHTML で完全クリアする

これが最も安全なベストプラクティスのようです。


この記事が気に入ったら『目黒で働く分析担当の作業メモ』ご支援をお願いします!

※OFUSEに飛びます


おすすめの記事