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)のものは、注記やらセグメント情報やらテンコ盛りなので半端なく巨大。function changeWord() {
str = FILE_DATA
document.getElementById("xbrl_code").innerHTML = str;
}
最初に試したのは 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);
}
}
しかし、効果はあまりなかった。。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);
}
でも、やっぱりうまくいかない。。。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はダメだけど)。function changeWord() {
str = FILE_DATA
var parent = document.getElementById("xbrl_code");
parent.innerHTML = "";
var div = document.createElement("div");
div.innerHTML = str;
parent.appendChild(div);
}
いきなり 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 の最適化は難しい。function changeWord() {
str = FILE_DATA
var div = document.createTextNode(str);
document.getElementById("xbrl_code").appendChild(div);
}
EDIUNET | PHP/MySQL | 独り言 | 提供サービス | JavaScript