table rowの並び替え(javascript)

JavaScript

テーブルのソートを実装するにあたって、どういったロジックにするのがいいのかを考えてみました。
ぱっと思い浮かんだ方法は2つ。

  1. tbody以下を消去して、並び替えたデータからDOMを再生成する。
  2. trをappendChildやinsert等で移動することで並び替えを行う。

データを並び替えてDOMを再生成する方法

オブジェクトの配列で適当なキーを指定してソートする場合、大体こんな実装になると思います。


const tbody = document.getElementById('tableBody');
const jaCollator = new Intl.Collator('ja');

data.sort((a, b) => {
    return jaCollator.compare(a[key], b[key]) ;
});

// tbody.innerHTML = '';
 これでも可だけど↓を推奨
while (tbody.firstChild) {
    tbody.removeChild(characterTable.body.lastChild);
}

createTableBody(data);

30列700行くらいのデータテーブルで試した時、Chromeで約100msecくらいかかりました。
なお計測時間は描画の完了までではなく、js上でcreateTableBodyが終わるまでの実行時間です。
描画完了はコンマ数秒かかっています。

今回のテーブルはtdの中にテキストがあるだけで割とシンプルな構造にしています(クラス付与したりdataAttributeつけたりはしてるけれども)が、再生成のロスが大きいのかな。複雑な構造していたらもっと時間かかりそう。

もともと存在するtr要素を移動する方法

よくあるケースはTableHeadをクリックしてソートするやつです。
多分こういう実装になると思います。

※thead等のonclickイベント内で

const index = e.target.closest('th').cellIndex;

Array.prototype.slice
.call(tbody.children)
    .sort((tr1, tr2) => tr1.children[index].textContent.localeCompare(tr2.children[index].textContent))
    .forEach(tr => tbody.appendChild(tr));

さっきはIntl.Collatorで今回はlocalCompareで書いているのに深い意味はありません。
機能としてはほぼ同等で、Intl.Collatorの方が少し速いらしいです。

で、同じく30列700行のテーブルで試したところ、約40msecくらいでした。
こちらも描画完了までの時間ではなく、forEachが終わるまでの時間です。
結果はこっちの方が早いですね。

最終行のtbody.appendChild(tr)しているところで再描画のロスが出るかと思ったんですが、fragmentにappendChildした後にtbody.appendChild(fragment)としても実行時間は変化なし。短時間の連続した処理に関しては内部で遅延描画するとかの最適化をされてそうです。

ただ、早いといっても60msecは体感では全くわからないのでデータ量10倍でも試してみました。
結果はほぼ同様で、それぞれ1000msec、400msec程度でした。
7000行くらいになるとはっきりDOM再生成の方が遅いです。

まとめ

意外なことにdom操作だけのソートが早かった。
元データを保持していなくてもソートできるので意外と便利に使えるかも。

一方でデータを保持していてDOMの生成は遅延させて行うような場合は、データのソート&DOM再生成を避けられない。

コメント