読者です 読者をやめる 読者になる 読者になる

さわっても熱くない花火

ちょっとした驚きを食べながら生きています

JavaScriptでHTML要素を大量に作る場合どのやり方が速いか

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個作ってそれにかかった時間を計測してみました。

こんな感じですね。 f:id:yanoshi:20160619201935p:plain f:id:yanoshi:20160619201956p:plain

コードはここにおいてます。

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を使う方法

developer.mozilla.org

ECMAScript6からTemplate stringsが使えます。控えめに言って最高ですね。

個人的にはこれが全てを解決してくれると信じているのですが、結果はどうなることか。

ちなみにFirefoxChromeは対応しているので、これからはこれが主流になるのでしょうね。

f:id:yanoshi:20160619185028p:plain

f:id:yanoshi:20160619185044p:plain

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)から知見の提供がありました。多謝。

そういえばdocumentFragment使うべきだなぁーとか思いましたまる (試してないけど





更に追記

実際にdocumentFragmentを使ってみた。

GitHub - yanoshi/dom_test at use_DocumentFragment

その結果………むしろ遅くなった。

  • React.js : 571744ms
  • cloneNode : 660ms
  • document.createElement : 410ms

勝手な予想だけど、今回みたいな凄くシンプルでCSS装飾も少ないHTMLの場合はdocumetFragmentを使うまでも無いんだろうなぁって感じ。

*1:やる気が無くなったので計測せず