jQuery ちょっと高速化に挑戦してみる

jQueryを使っていると「なんだかもっさりするな。。。」と思う場合があります。こういう場合、書き方を見直す事で、かなり改善する場合があります。今回は「ちょっと高速化」を目標に書き方のポイントを紹介してきます。

■ 要素選択の記述を見直す

jQueryはセレクタやトラバースメソッドを使って要素を選択しますが、書き方によって大幅にパフォーマンスが上がる場合があります。

下記HTMLの中の「※ ここを見つける ※」というテキストを「■■■ 書き換えたよ ■■■」に入れ替えたい場合、どう書きますか?

<script>
$(function(){
	○○○.text("■■■ 書き換えたよ ■■■");
});
</script>

<div id="wrap">
	<div id="content">
	<h1>タイトル</h1>
	<p class="text">テキスト..................</p>
	<div class="text">※ ここを見つける ※</div>
	<div class="text">テキスト..................</div>
	</div>
</div>

テキストの書き換えは「.text(“■■■ 書き換えたよ ■■■”)」で書き換えることができます。その前の要素を選択するところ(○○○)を考えて見て下さい。

おそらく人によって書き方がかわってくると思います。思いつくものを、ざっと下記に上げてみました。

<script>
$(function(){
	$("p + div").text("■■■ 書き換えたよ ■■■"); //0.679秒
	$("p").next().text("■■■ 書き換えたよ ■■■"); //0.693秒
	$("#content").find("div").first().text("■■■ 書き換えたよ ■■■"); //0.746秒
	$("#content div").eq(0).text("■■■ 書き換えたよ ■■■"); //0.767秒
	$("div.text").first().text("■■■ 書き換えたよ ■■■"); //0.762秒
	$("#content div").first().text("■■■ 書き換えたよ ■■■"); //0.778秒
	$("p").next("div").text("■■■ 書き換えたよ ■■■"); //0.808秒
	$("div#content").find("div").first().text("■■■ 書き換えたよ ■■■"); //0.945秒
	$("div.text:contains('※ ここを見つける ※')").text("■■■ 書き換えたよ ■■■"); //1.164秒
	$("div.text:first").text("■■■ 書き換えたよ ■■■"); //1.803秒
	$(".text").eq(1).text("■■■ 書き換えたよ ■■■"); //0.697秒
	$("div.text").eq(0).text("■■■ 書き換えたよ ■■■"); //0.753秒
});
</script>

後ろに添えている「//○○秒」は繰り返し同じ処理をさせたときにかかった時間です(Firefox7で10000回ループでチェック)。上から順番に実行結果の速い順に並べています。一番速いものに比べ、遅いものは3倍近く時間がかかっています。上記の動作確認ページが下記です。

jQuery 高速化のための記述テストサンプル

このように要素取得の書き方の違いだけで、これだけの差がでてきます。では具体的に書き方のポイントを見ていきましょう。

まず下記2つ。

	$("div.text:first").text("■■■ 書き換えたよ ■■■"); //1.803秒
	$("#content div:first").text("■■■ 書き換えたよ ■■■"); //1.815秒

遅い原因は「:first」です。「一番最初のもの」という指定ですが、かなり実行に時間がかかります。これは「.first()」を使うことでかなり速度が上がります。「:○○」系のものは、実行に時間がかかってしまうものがいくつかあるようなので、代用できるメソッドが用意されている場合は、そちらに書き換えましょう。

次に下記を見比べて下さい。

	$("#content").find("div").first().text("■■■ 書き換えたよ ■■■"); //0.746秒
	$("div#content").find("div").first().text("■■■ 書き換えたよ ■■■"); //0.945秒

違うのはセレクタに「div」を付けているかどうか、というだけです。この記述だけで20%以上速度が変わっています。id属性はページ内に一意(一個だけ)というルールがあるので、HTMLタグを指定する必要はありません。id属性を検索する場合はHTMLタグを付けないようにしましょう。

「 $(id属性) 」は「 $(HTMLタグ) 」と指定するよりも速度が上がります。もしid属性が割り当てる事ができるのであればHTMLタグにidを割り当て利用してみてください。

では次。

	$("#content").find("div").first().text("■■■ 書き換えたよ ■■■"); //0.746秒
	$("#content div").first().text("■■■ 書き換えたよ ■■■"); //0.778秒

「$(“#content”).find(“div”)」「$(“#content div”)」はともに「id contentの要素の中のdivを選択」という記述ですが、「find()」を利用した方が少し速くなります。書く時には「$(“#content div”)」の方が見やすいので、自分自身こちらを使う事が多いのですが、少しでも速度UPをしたい方は「find()」を使うようにしましょう。

次はclass属性の指定について。

	$(".text").eq(1).text("■■■ 書き換えたよ ■■■"); //0.697秒
	$("div.text").eq(0).text("■■■ 書き換えたよ ■■■"); //0.753秒

「class属性を指定する場合はHTMLタグも指定する方が速いよ!」と覚えている方もいるかと思いますが、基本IEのバージョン8以下の場合です。もしスマートフォンのみをターゲットにした場合、HTMLの記述(classの指定箇所やHTMLタグの数)次第では、HTMLタグ指定はない方が速度UPすることがあります。

と、ここまでをまとめると、

・「:○○」系のものは、代用できるメソッドや書き方があれば、そちらを利用する。

・ id属性にHTMLタグは付けない。もしHTML側でid属性を付けることができるなら、積極的に利用する。

・ find() メソッドは積極的に利用する。

・ class属性にHTMLタグを添えるとIEのバージョン8以下では速くなる。それ以外ブラウザの場合はHTMLタグがない方が速度UPの可能性あり。

となります。

■ 繰り返す記述を見直す

「繰り返す記述」というのは何回もプログラムを実行する記述箇所です。下記の悪い例、良い例を見比べて下さい。

【悪い例】

	$("#content").append("<p>書き換え済み</p>");
	$("#content").find(".text").css("color","red");
	$("#content").find(".text").text("■■■ 書き換えたよ ■■■");

【良い例】

	var content = $("#content");
	var content_text = content.find(".text");
	content.append("<p>書き換え済み</p>");
	content_text.css("color","red").text("■■■ 書き換えたよ ■■■");

悪い例では「 $(“#content”) 」が3回、「 $(“#content”).find(“.text”) 」が2回記述されています。「 $(“….”) 」と記述すると指定された要素を抜き出す処理が実行されます。「 $(“#content”) 」が3回記述されているといことは、同じものを3回抜き出す処理が実行されているということです。

良い例では、抜き出したものを変数に入れ、その後は変数の中身を利用することで、何度も同じ処理を実行しないようにしています。

また良い例の方では「.css(“color”,”red”).text(“….”)」と処理をつなげています。同じ要素に対して複数の処理をしたい場合、「.(ドット)」でつなげて記述することができます(これをメソッドチェーンといいます)。つなげられるものは、つなげて書いてみましょう。

次は、やりがちなfor文の中で何度も処理を実行する例です。

【悪い例】

	for(var i=0; i<10; i++){
		$("#content").append("<p>テキストを追加しました。</p>");
	}

【良い例】

	//new Array() = データを入れる配列を生成
	var add_data = new Array();
	for(var i=0; i<10; i++){
		//.push データを配列に入れる
		add_data.push("<p>テキストを追加しました。</p>");
	}
	//add_data.join('') 配列を結合
	$("#content").append(add_data.join(''));

「id content 内に10回コンテンツを追加する」という記述です。

悪い例では「 $(“….”) 」「 append(“….”) 」の2つを10回実行しているので、処理に時間がかかります。これを良い例の方では、それぞれ1回のみ実行するように書き換えています。

良い例のコメントをつけている部分はjQueryではなく、素のJavascriptの記述です。毎回 append でデータを追加するのではなく、追加するデータを一旦配列に入れ、そのデータをまとめて最後に追加しています。

もしfor文やwhile文内で append 等で追加している場合は、良い例のようにforの外で追加するよう書き直してみてください。処理速度は数倍、数十倍UPするはずです。

上記2つの例のように同じ処理を何度もさせるような書き方をしている場合は、できる限り処理が少なくなるような書き方に直しましょう。

■ 最後に処理時間の計り方

実際に書き直しをすると、どのくらい速くなっているか知りたい!と思うはず。たいしたものではないですが、すぐに計測できるようスクリプトを付けておきます。これを利用して今どれくらい時間がかかっているのか、書き直してどのくらい速くなったか確認してみてください。

<script>
$(function(){
	//繰り返す回数
	var end_count = 1000;
	var start_time = new Date();
	for (var start_count=0; start_count<end_count; start_count++){
		//実行する処理を書く
	}
	var end_time = new Date();
	var msec = (end_time - start_time)/1000;
	alert("結果:"+ msec +"秒かかりました");
});
</script>

ふう。。。長くなりましたが、以上の書き方を見直せば、ちょっとどころではなく、えっ!と驚く程速くなるかもしれません。ぜひぜひ高速化に挑戦してみてください。

※ちなみにjQuery本体はできるだけ新しいものを利用した方が速くなる可能性があります。ですが、プラグインが動作しなくなったり、未知のバグがあったりするので、jQuery本体を差し替える場合は要注意です。しっかり動作確認してから差し替えましょう!

jQuery – 目的の場所にさくっと要素を追加する

いろんなセレクタ指定方法+αを覚えて、目的の要素をさくっと取得する」で要素をさくっと選択できるようになったら、次は選択した要素を元に、さくっと新しく要素を追加する方法を覚えて見ましょう。

「新しく要素を追加する」というのはAjaxで外部データを読み込み、リスト一覧を表示させる場合などに頻繁に利用します。たとえばtwitterからデータを読み込んでサイドバーに表示する、といった場合です。

他にもホームページに新しい商品を追加して「NEW」という画像を表示することがあると思いますが、一ヶ月後には消したいという場合、直接HTMLに書き込んでいると、いちいち修正しないといけません。それをうまくJavascriptで追加するようにして上げれば、修正の必要がなくなります。

class名に日時を添えて、そのclass名につけられた日時と現在の日時と比べて処理をさせます。少しスクリプトは荒い例ですが、下記のような感じです。

<script>
$(function(){
	var now = new Date(); //現在の日時を取得
	var h1Class = $("h1").attr("class"); //h1のclassを取得
	var aDate = h1Class.replace("new","").split("-"); //取得したclassを整形
	var add_date = new Date(aDate[0], aDate[1], aDate[2]); //Dateオブジェクトに変換
	//もし現在が日時がclassで指定した日時より前なら画像を表示
	if(now < add_date) $("h1.new2011-12-21").append('<img src="new.gif">');
});
</script>
<!-- class名に画像の表示年月日を付ける -->
<h1 class="new2011-12-21">新色 ふかふか手編みマフラー</h1>

このように新しく要素を追加する方法を知っておくと、ちょっとしたスクリプトで手間を省く事ができます。

新しい要素を追加するといっても、対象の要素の前に追加、中に追加、包み込む、等いろいろなものが用意されています。ではではセレクタのとき同様、動作サンプル&簡易説明書きをチェックしてみてください。

jQuery DOM操作(Manipulation)系メソッド動作サンプル

[ DOM操作(Manipulation) ]

$(“div.box”).after(“<div>…</div>”)
CLASS名 box が指定されたdivタグの後に<div>…</div>を追加

$(“<div>…</div>”).insertAfter(“div.box”)
<div>…</div>をCLASS名 box が指定されたdivタグの後に追加

$(“div.box”).before(“<div>…</div>”)
CLASS名 box が指定されたdivタグの前に追加

$(“<div>…</div>”).insertBefore(“div.box”)
<div>…</div>をCLASS名 box が指定されたdivタグの前に追加

$(“div.box”).append(“<div>…</div>”)
CLASS名 box が指定されたdivタグの子要素の最後に<div>…</div>を追加

$(“<div>…</div>”).appendTo(“div.box”)
<div>…</div>をCLASS名 box が指定されたdivタグの子要素の最後に追加

$(“div.box”).prepend(“<div>…</div>”)
CLASS名 box が指定されたdivタグの子要素の最初に<div>…</div>を追加

$(“<div>…</div>”).prependTo(“div.box”)
<div>…</div>をCLASS名 box が指定されたdivタグの子要素の最初に追加

$(“div.box”).wrap(“<div>…</div>”)
CLASS名 box が指定されたdivタグを<div>…</div>をで包み込む
(複数ある場合は各それぞれを包み込む)

$(“div.box”).wrapAll(“<div>…</div>”)
CLASS名 box が指定されたdivタグすべてを<div>…</div>をで包み込む
(複数ある場合はすべてを一つに包み込む)

$(“div.box”).wrapInner(“<div>…</div>”)
CLASS名 box が指定されたdivタグの子要素を<div>…</div>をで包み込む

$(“div.box”).clone().appendTo(“div.boxA”)
「$(“div.box”).clone()」でCLASS名 box が指定されたdivタグを複製
「appendTo(“div.boxA”)」で複製したものを、CLASS名 boxA が指定されたdivタグの子要素の最後に追加

$(“div.box”).replaceWith(“<div>…</div>”)
CLASS名 box が指定されたdivタグを<div>…</div>に入れ替える

$(“div.box”).remove()
CLASS名 box が指定されたdivタグを削除(中身もすべて)

今回はManipulation系の中でも新しく要素を追加するものを抜粋して紹介しています。本家ドキュメントではカテゴリが細かく分けられていますが、新しく要素を追加するものはほぼ紹介できていると思います。その他のManipulation系メソッドも知りたい方は、ぜひ本家ドキュメントをのぞいて見て下さい。

Manipulation – jQuery API

jQuery – いろんなセレクタ指定方法+αを覚えて、目的の要素をさくっと取得する

ずいぶん以前にセレクタについて書いたのですが、今回はセレクタの種類を抜粋して、記述例と説明書きにまとめてました。「セレクタとは?」という方は、まず下記記事を読んでみて下さい。

jQueryを使ってみよう。(基本編 セレクタ1)

+αというのはトラバース(走査)系のメソッドです。「この要素を抜き出したい!」と思ったときにセレクタだけでは抜き出しにくい場合があります。そういうときにトラバース(走査)系のメソッドを使うと、簡単にできたりするので覚えておくと便利なものです。

「こういう書き方をすると、こういう要素が選択される」という例で説明を添えていますが、数が多いので説明書きは、ほんとうに添えた程度です。説明だけではわかりにくいと思うので動作サンプルを用意しました。動作サンプルを眺めながら、ちらっと説明書きをチェックしてもらえれば「なるほど!」となるはず。。。ご利用ください。

jQuery セレクタ&トラバース動作サンプル

[ セレクタ ]

$(“body”)
bodyタグの要素を選択

$(“#id1”)
ID名 id1 の要素を選択

$(“.class1”)
CLASS名 class1 の要素を選択

$(“.class1 .class2”)
CLASS名 class1 の要素の中にあるCLASS名 class2 の要素を選択

$(“.class3, .class4”)
CLASS名 class3、もしくはCLASS名 class4 の要素を選択

$(“.class5”, “#id1”)
ID名 id1 の要素の中にあるCLASS名 class5 の要素を選択

$(“.class6.class7”)
CLASS名 class6 と class7 2つ持つ要素を選択

$(“a[href]”)
aタグのhref属性がある要素を選択

$(“a[href = ‘#pagetop’]”)
aタグのhref属性の値が「#pagetop」の要素を選択

$(“a[href != ‘#pagetop’]”)
aタグのhref属性の値が「#pagetop」でない要素を選択

$(“a[href ^= ‘#link’]”)
aタグのhref属性の値が「#link」から始まる要素を選択

$(“a[href $= ‘bottom’]”)
aタグのhref属性の値が「bottom」で終わる要素を選択

$(“a[href *= ‘page’]”)
aタグのhref属性の値に「page」が含まれている要素を選択

$(“ul li:first”)
すべてのulタグ内をあわせたliタグの中の最初の要素を選択

$(“ul li:first-child”)
各ulタグ内にあるそれぞれのliタグの最初の要素を選択

$(“ul li:last”)
すべてのulタグ内をあわせたliタグの中の最後の要素を選択

$(“ul li:last-child”)
各ulタグ内にあるそれぞれのliタグの最後の要素を選択

$(‘li:not(“.class6”)’)
liタグでCLASS名 class6 が指定されていない要素を選択

[ トラバース ]

要素については先祖、親子、兄弟という階層が存在します。

<div>
	<ul>
		<li>兄</li>
		<li>この要素から見て....ul要素は親、div要素は先祖</li>
		<li>弟</li>
	</u/>
	ul要素からみるとli要素は子、div要素は親ということになります。
</div>

以下説明に子要素や兄要素等の言葉がでてきますが、簡単に説明すると上記のような感じなのでイメージできれば簡単です。

$(“#id1”).find(“.class7”)
ID名 id1 の要素の中にあるCLASS名 class7 の要素を選択

$(“ul”).children(“.class6”)
ulタグ の子要素にある CLASS名 class6 の要素を選択

$(“li.class6”).parent()
CLASS名 class6 があるliタグを持つ親要素を選択

$(“li.class6”).parent(“#id1”)
CLASS名 class6 があるliタグを持つ親要素でID名 id1 の要素を選択

$(“li.class6”).next()
CLASS名 class6 があるliタグに隣接する弟要素を選択

$(“li.class6”).next(“.class7”)
CLASS名 class6 があるliタグに隣接する弟要素の中でCLASS名 class7 の要素であれば選択
(それ以外であれば選択しない)

$(“li.class7”).prev()
CLASS名 class7 があるliタグに隣接する兄要素を選択

$(“li.class7”).next(“.class6”)
CLASS名 class7 があるliタグに隣接する兄要素の中でCLASS名 class6 の要素であれば選択
(それ以外であれば選択しない)

$(“a[href ^= ‘#link’]”).closest(“li”)
aタグのhref属性の値が「#link」から始まる要素の最も近いliタグ(親要素)を選択

$(“li”).slice(“3,12”)
すべてのliタグをあわせた0から数えて3個目から12個目までの間の要素を選択

$(“li”).find(“a”).css({color: ‘white’}).end()
liタグ内にあるaタグにスタイルを追加した後、直前の要素を選択

$(“li”).not(“li.class6”)
すべてのliタグでCLASS名 class6 が指定されていない要素を選択

$(“a[href *= ‘middle’]”).first()
aタグのhref属性の値に「middle」が含まれている一番最初にある要素を選択

$(“a[href *= ‘middle’]”).last()
aタグのhref属性の値に「middle」が含まれている一番最後にある要素を選択

$(“p”).siblings()
pタグの兄弟要素を選択

$(“p”).siblings(“p”)
pタグの兄弟要素の中でpタグの要素を選択

この他にもCSS2、3系のセレクタ、フォーム関連のセレクタ等、まだまだたくさんあります。もっと詳しく動作内容を知りたい場合は、jQuery本家ドキュメント「Selectors – jQuery API」「Traversing – jQuery API」で確認してみてください。