yanoshiです。相変わらずお財布が寒いです。案外学生の時よりお金が無いかもしれませんね。困る…
近頃何故かJavaScriptを触る機会が多くてですね。
jQueryに親を殺された私はgetElementByIdとquerySelectorAllを叩く日々をしていました。
そんなことをしていると悩むのがHTML要素を作る方法。
何番煎じかもはやわからない検証ですが、適当にコードを書いてみたのでメモ書きを残しておきます。
計測方法
<div> <span style="color: rgb(x,x,x);">hoge○</span> <a href="#" onclick="alert('fuga○');">Click!</a> </div>
みたいな要素を10000個作ってそれにかかった時間を計測してみました。
こんな感じですね。
コードはここにおいてます。
GitHub - yanoshi/dom_test: JavaScriptでHTML要素を作りまくるテスト
計測環境
Google Chrome バージョン 51.0.2704.103 m
- CPU:
Intel Xeon E5450 2CPUs(2.91GHz 8Cores)
- メモリー:
DDR2 FD-DIMM 24GB
試す手法一覧
document.createElementを使った方法
一番ブラウザに優しそうな方法ですね。
document.createElement()
でノードを作ってnode.appendChild()
していくアレです。
しかしながらめんどくさいんですよねーはい。
dom_test/js1.html at master · yanoshi/dom_test · GitHub
dom_test/1.js at master · yanoshi/dom_test · GitHub
insertAdjacentHTMLとString.replaceを使った方法
怠惰を極めた時に割とやりがち。お行儀悪いのはわかってるんだけどなぁ…
以下みたいなHTMLを埋め込んでおいて…
<div id="hidden"> <div> <span style="color: rgb({color},{color},{color});">{span}</span> <a href="#" onclick="{event}">{linktext}</a> </div> </div>
こんなJavaScriptを書くアレね。
document.getElementById("output").insertAdjacentHTML("beforeend", document.getElementById("hidden").innerHTML .replace("{span}", "hoge") .replace("{event}", "alert('fuga');" ) .replace("{linktext}","Click!") .replace(/{color}/g, 128) );
いちいちブラウザはHTMLを解釈する必要が出てくるので、結構遅くなるんじゃないかと予想。
ちなみに怠惰を極めすぎてnode.innerHTML += foo;
みたいな書き方をした時はほんとに遅かったので、ちゃんとinsertAdjacentHTML()
を使いましょうね。
dom_test/js2.html at master · yanoshi/dom_test · GitHub
dom_test/2.js at master · yanoshi/dom_test · GitHub
cloneNodeを使う方法
前述の方法を少しお行儀よくした感じ。
cloneNode()
した後にquerySelector()
で要素選択していじる感じ。
createElement()
を使っている方法とそんなに差があるのかって感じもしなくもないけど、モック作るときに作ったデザインコードをそのまま使いまわせてそれなりにお行儀が良い気がするので割と好みだったり。
dom_test/js3.html at master · yanoshi/dom_test · GitHub
dom_test/3.js at master · yanoshi/dom_test · GitHub
React.jsを使う方法
その昔「VirtualDOMだぜひゃっふー!はやい」と持て囃されたReactだけど、近頃でも速度優位性があるのかな?と思って取り敢えず使ってみました。
正直Ajaxのコールバックでどむどむする時に便利ってだけで、速度的優位性はなんじゃないかなぁーとか思っているんだけどどうなんだろうねーとか思ったり(あんまりReact.jsの事をよく知らない。ってかJavaScriptのこともよく知らないのっ////)
ただ、React.jsにはappendChild()
的にrender()
する方法が無いようなので、createElement("div")
した後にそのオブジェクトに対してrender()
してます。
すなわちdiv要素が外側もう一つできちゃっているので…これが公平な比較なのかは割と謎です。
dom_test/js4.html at master · yanoshi/dom_test · GitHub
dom_test/react.jsx at master · yanoshi/dom_test · GitHub
Template stringsを使う方法
ECMAScript6からTemplate stringsが使えます。控えめに言って最高ですね。
個人的にはこれが全てを解決してくれると信じているのですが、結果はどうなることか。
ちなみにFirefoxとChromeは対応しているので、これからはこれが主流になるのでしょうね。
dom_test/js4.html at master · yanoshi/dom_test · GitHub
dom_test/4.js at master · yanoshi/dom_test · GitHub
結果
方法 | 1回目 | 2回目 | 3回目 | 平均 |
---|---|---|---|---|
document.createElement | 332ms | 391ms | 411ms | 378ms |
insertAdjacentHTML+String.replace | 342ms | 344ms | 386ms | 357.33ms |
cloneNode | 641ms | 689ms | 605ms | 645ms |
React.js | 565481ms | -*1 | - | - |
Template strings | 301ms | 333ms | 314ms | 316ms |
総評
Reactが想像以上の遅さを発揮してくれました。(予想外です…)
良い書き方をしてなかったのかなぁ…
cloneNode
を使った方法がinsertAdjacentHTML+String.replace
より遅かったのはちょびっと驚きでした。
とはいうものの、多分ですがCSSで色々とデザインを定義しまくっているとcloneNode
が逆転するんじゃないかなって気もしてます。
ちなみにFirefoxで計測した場合は、
document.createElement
= Template strings
< insertAdjacentHTML+String.replace
< cloneNode
< React.js
って感じでした。
まとめ
「なんだー怠惰な書き方でもそれなりにパフォーマンスあるじゃん。」
追記
JavaScript力ましまし系男子のきくらげ氏(@Kiikurage)から知見の提供がありました。多謝。
@yanoshi 要素数が十分多いなら、cloneNodeを要素自身じゃなくて親要素に適用して倍々に増やせばO(log N)でめっちゃ早いですよ
— きくらげ (@Kiikurage) 2016年6月19日
@yanoshi @Kiikurage メインのドキュメントツリーへの追加にdocumentFragmentを使うことで、appendChildの回数が減らせるので早くなります。レイアウトの再計算にまつわるコストが大きいので。
— きくらげ (@Kiikurage) 2016年6月19日
@yanoshi ちなむとフラグメントに溜め込んでからメインツリーへアペンドする方法だけで、だいぶ早くなると思いますよ
— きくらげ (@Kiikurage) 2016年6月19日
そういえばdocumentFragment使うべきだなぁーとか思いましたまる
(試してないけど
更に追記
実際にdocumentFragmentを使ってみた。
GitHub - yanoshi/dom_test at use_DocumentFragment
その結果………むしろ遅くなった。
- React.js : 571744ms
- cloneNode : 660ms
- document.createElement : 410ms
勝手な予想だけど、今回みたいな凄くシンプルでCSS装飾も少ないHTMLの場合はdocumetFragmentを使うまでも無いんだろうなぁって感じ。
*1:やる気が無くなったので計測せず