innerHTMLで巨大な要素を高速に変更する方法 (javascript tips)

※以前別の場所で書いた文章を備忘的に書き記しておきます。

【投稿年月日】2011-03-03 【ジャンル】JavaScript

 「XBRL for PHP」を開発中、innerHTML で要素を書き換える必要が出てきた。ところが、書き換える文字列があまりにも巨大なため、処理中にブラウザが固まってしまう。
 javascriptのコードは以下のような感じ。global な FILE_DATA を再利用しながら「id=xbrl_code」の要素を書き換える(実際のコードは「str = FILE_DATA」において加工処理がなされている)
var FILE_DATA;

function changeWord() {
 str = FILE_DATA
 document.getElementById("xbrl_code").innerHTML = str;
}
 この FILE_DATA がさほど大きくなければ問題ないが、XBRLファイルは例外なく大きい。しかもアメリカ(edgar)のものは、注記やらセグメント情報やらテンコ盛りなので半端なく巨大。

 最初に試したのは createTextNode() で代替すること。改行(\n で配列に格納して出力)と半角スペース(  ではなく \u00A0 を使用)を表示する必要があるので工夫が必要だが以下のような形で実現。

1.createTextNode() を使う

var FILE_DATA;

function changeWord() {
 str = FILE_DATA
 var parent = document.getElementById("xbrl_code");
 var lines = str.split(/\n/);
 for(var i=0, len=lines.length; i<len; i++) {
  var div = document.createTextNode(lines[i]);
  parent.appendChild(div);
  var br = document.createElement("br");
  parent.appendChild(br);
 }
}
 しかし、効果はあまりなかった。。

 次に試したのは、createTextNode() に加えて createDocumentFragment() を使用すること。数が多いので効果があると思われた。

2.createTextNode() と createDocumentFragment() を使う

var FILE_DATA;

function changeWord() {
 str = FILE_DATA
 var df = document.createDocumentFragment();
 for(var i=0, len=lines.length; i<len; i++) {
  var div = document.createTextNode(lines[i]);
  df.appendChild(div);
  var br = document.createElement("br");
  df.appendChild(br);
 }
 var parent = document.getElementById("xbrl_code");
 parent.appendChild(df);
}
 でも、やっぱりうまくいかない。。。

 この他にも replaceChild() を使う方法とかいろいろ試してみたけれども、どれもこれもダメ。完全に行き詰っていたところ下記サイトを発見。

innerHTML がメモリリークを引き起こす例
www.revulo.com/blog/20080620.html

 藁にもすがる思いで上記サイトを参考に以下のようなコードを試してみる。

3.innerHTML () と DOM を併用

var FILE_DATA;

function changeWord() {
 str = FILE_DATA
 var parent = document.getElementById("xbrl_code");
 parent.innerHTML = "";
 var div = document.createElement("div");
 div.innerHTML = str;
 parent.appendChild(div);
}
 おっ、FireFoxで固まらずに動いた。IE8でも大丈夫(IE6やIE7はダメだけど)。
 いきなり innerHTML するのではなく、新たに作成した要素の innerHTML に巨大データを入れ、子要素として appendChild() するというやり方が、ブラウザにとっては嬉しいらしい。無駄なプロセスを踏んでいると感じるし、なぜこの手法が効果的かも分からないが、取り合えず助かった。

 なお、今回と違い、HTMLタグなどを使用しない場合は、下記の通り、素直に createTextNode() を使うのが無難。
var FILE_DATA;

function changeWord() {
 str = FILE_DATA
 var div = document.createTextNode(str);
 document.getElementById("xbrl_code").appendChild(div);
}
 それにしてもjavascript の最適化は難しい。

EDIUNET | PHP/MySQL | 独り言 | 提供サービス | JavaScript