昔聞いた先輩社員の名言
- DOM国とJavaScript国の間の橋は狭いので、頻繁に行き来しないように気をつけろと言われたのを思い出したので、ある意味初歩的なことではあるが、自戒も込めて記事にしておく
先に結論
- 要は
DOM操作は遅いので、JavaScriptの(特にループの中で)DOM操作を頻繁に行うと性能劣化につながる
というお話
経緯/背景
- 私の所属するプロジェクトの終盤で、性能問題が顕在化した
- パッケージ製品を使っており、パッケージの大幅な更改があったのだが、その前後で画面描画が倍以上遅くなっている
- パッケージが新しくなっているのに、よく使うデフォルト的な画面が圧倒的に遅くなるのはありえない。1ヶ月以内になんとかしろと言うことに
- その画面は表形式のデータを表示する画面で、数千行(一度の画面描画は150件程度)が処理対象になる
- 列は10項目程度
原因
- ソースコードレベルまで追跡した結果以下2点が判明
1. 画面描画時にリサイズイベントが呼ばれ、同じ関数が2回呼ばれている
- 画面ロード時に自身とリサイズイベントで関数が2回呼ばれている。
- ただ実は、これは体感的には問題にならない
- なぜなら呼ばれる関数は描画に関数するもので、1回目が終わればレンダリングが行われ、その後の関数の再呼び出しと再レンダリングは見栄え上何も変わらないためである。
2. 二重ループの中でDOM操作
こちらが問題の本質
- 画面に表示される表の各セルの文字が見きれないように、それぞれの幅を計算して描画幅を調節している
- その過程で、行のループの中の列のループでセルごとに処理を実施
- セルごとに以下の処理を行う
- spanタグを作成
- append
- 幅を計測
- remove
- そのため、DOMアクセスが数万回単位の凄まじい回数で行われ、追加/削除も伴うため、DOM再構築も頻繁に行う
解決法
どちらも結構シンプルです
- そもそも別のイベントなので、リサイズのイベントを呼ばないようにする
- 毎回DOMの生成などをせずに、cssやその他のものを使って幅を計算する
- 今回は幅の問題でしたが、それ以外でもDOM操作を一括で行う。画面描画分だけを対象にするなど、工夫はいくらでもできると思います。
結果、画面描画に10秒近くかかっていたのが、1秒未満になりました。
某キャラ風にいうならば
今回の件から俺が得るべき教訓は、
DOM国とJavaScript国の橋は狭く輸出入のコストが高いので、エンジニアという監察官が、通して良いものと通さないものをきちんと吟味しないと、両国が損をすることになる。そしてそれは、流通量が増えたときにしか気づかず、そのときにはすでに手遅れになっている。
ということだ
ってことですね。