経緯
JavaScriptには、DOMを操作する方法がいろいろある。
私が初めてJavaScriptを触ったときのことだ。
ネイティブなJSと外部ライブラリ(jQuery)との区別がついておらず、混乱したことを覚えている。
検索だとDOM操作のやり方がいろいろ出てきて、結局どの方法で操作すればいいのかよくわからない。いろいろあるけど何が違うの?、となったものだ。
同じような人が迷わないで済むように、どれを使えばよいのか書き残しておく。
ここで述べる原則に従えば、フレームワークを使わないVanillaなJSでも十分に、もしくはそれ以上に処理が速くなるはずだ。
速いDOM操作の大原則
- レンダリングはまとめて行うこと。
- DOM操作は、ネイティブなJSの関数を使うこと。(jQueryは使わない)
ネイティブなJSのどの関数を使えばいいかなど、具体的な実装方法について以下述べていく。
DOM要素取得の方法
ネイティブなJSには、DOM要素の取得を行う関数として、getElementシリーズと、querySelector系とがある。
状況によってどれを使えばよいかを説明していく。
id指定で要素を取得
getElementByIdを使う。
var element = document.getElementById("id名");
idから要素を取得するときは、getElementByIdが最速。悩む必要はない。
name, class, tagなどを指定して、要素を一つだけ取得
querySelectorを使う。
var element = document.querySelector("セレクタ");
//セレクタの例
//querySelector("[name=名前]"); //name属性値指定
//querySelector("div.classA"); //<div class="classA">を取得
CSSセレクタの記述方法の詳細については以下を参考
name, class, tagなどを指定して、複数の要素を取得
getElementsByシリーズを使用する。elementsと複数形なのに注意。
var elements = document.getElementsByName("名前"); var elements = document.getElementsByClassName("クラス名"); var elements = document.getElementsByTagName("タグ名");
ただし、複数条件を指定して取得したい場合、querySelectorAllを使う
var nodes = document.querySelectorAll("セレクタ");
詳しい違いは要検索。参考:getElementsByTagNameとquerySelectorAllの違い
速いDOM操作のやり方
JSに限らず、プログラミング初心者がやりがちな無駄として「重い処理を不必要に繰り返し行っている」というものがある。
「不必要に重い処理を繰り返さない」という基本さえ守れば、どのようにDOM操作をしようが、速度にあまり違いはでない。
そして、JSにおける重い処理の代表が、ブラウザのレンダリング処理である。
したがって、レンダリングは一度にまとめて行うということさえ意識しておけば、問題ない。
以下に無駄処理のあるコード例とその改善例を挙げる。
ループ毎にDOMツリーを変更し、その度にレンダリングが行われる悪い例だ。
//悪い例------------------------------------------------------ for(var i=0; i<data.length; i++){ //要素を検索して取得(ループの度に毎回) var ul = document.getElementById("ul"); //innerHTMLによって、DOMツリーを変更 //レンダリング処理をループの度に毎回実施する ul.innerHTML += "<li>" + data[i] + "</li>"; }
これをDOMツリーの変更が一度だけになるように修正する。
//改善例------------------------------------------------------ var htmlstr = ""; for(var i=0; i<data.length; i++){ htmlstr += "<li>" + data[i] + "</li>"; //値をまとめる } //DOM要素の取得。(一度だけ) var ul = document.getElementById("ul"); //DOMツリーを変更し、レンダリングを実施。(一度だけ) ul.innerHTML += htmlstr;
このようにレンダリング処理を減らすことで処理を高速化できる。
さまざまなDOM操作の使い分け方
残りは、細かい話になる。
いろんな方法があるDOM操作について、状況による使い分けを説明する。
DOMを操作する方法には、createElement()などのDOM操作メソッドを使う方法と、element.innerHTMLなどDOM要素の持つ値を書き換えて、パースさせる方法とがある。
まず、はじめに言っておくと、DOM操作メソッドと、DOM要素の値の書き換えに、処理速度の違いはほとんどない。同速である。
なので処理速度のために、innerHTMLをわざわざDOM操作メソッドに書き換えるというようなことは必要ない。
ただし、いくつか補足と注意事項がある
XSS対策
innerHTMLはXSSの心配がある。対策を忘れずにしよう。
一方、DOM操作メソッドはXSSの心配がない。
DOM要素のテキスト変更のみしたいとき
テキストだけを変更したい場合には、innerTextやtextContentを使うと便利だ。
innerTextのコード例
element.innerText = "テキスト";
innerTextであれば、htmlパースは行われないため、XSSの心配がない。
改行コードを<br>タグに変換して出力もしてくれる。
また、改行コードの<br>タグへの変換が不要なのであれば、textContentを使うとさらに処理が軽い。もちろんXSSの心配はない。
element.textContent = "テキスト";
同じ構造を繰り返し使うとき
同じ構造を繰り返し生成する場合は、あらかじめその構造をテンプレートとして用意しておき、cloneNode()やimportNode()するとよい。
例えば、templateタグを使った例。(templateタグはIE非対応のため注意)
<ul id="ul"></ul>
<template id="template">
<!-- 必要なhtmlをまとめて記述 -->
<li></li>
</template>
<script>
var fragment = document.createDocumentFragment();
var template = document.getElementById('template');
for(var i=0; i<100; i++){
var clone = template.content.cloneNode(true); //templateを複製
clone.querySelector("li").textContent = "処理の例" + i;
fragment.appendChild(clone); //fragmentに追加することでレンダリングを避ける
}
document.getElementById("ul").appendChild(fragment); //レンダリング処理はここだけ
</script>
DOM操作方法のまとめ
- テキストのみのとき(改行なし)
textContent - テキストのみのとき(改行あり)
innerText - htmlにしたいとき
innerHTML(XSS対策必要)
createElement()
cloneNode()かimportNode()
fragmentの使用(レンダリングせずにDOMツリー構造を作成する)
というように、使い分ける。
ちなみにDOM操作メソッドの例についてはこちらのサイトが簡単にまとまっていて便利です。
また、JSの最適化には、次のサイトを参考にしました。さらに詳しく知りたい方はどうぞ
DOM操作の最適化によるJavaScriptチューニング(後編)
コメント