tech.kayac.com

最近Androidとの抗争が激化しているago@kyo_ago)です。

jQueryはCSSセレクタを多用する特徴がありますが、jQuery内では実行ブラウザやCSSセレクタの記述によって呼び出されるブラウザAPIが変わり、それによって実行速度にも影響が出ます。

この記事では「セレクタAPIとはなにか」、「CSSセレクタの記述によって呼び出されるセレクタAPIの種類」、「高速なセレクタAPIを使用するための方法」、「高速なセレクタAPIが使われるかどうか確認する方法」などを紹介したいと思います。

(※この記事はJavaScript Advent Calendar 2011 (フレームワークコース) : ATNDの1日目の記事です)

セレクタAPIとはなにか

セレクタAPIとは「#hoge .huga」のようなCSSセレクタから、DOM上に存在する要素を取得するためのAPIです。

jQueryではセレクタAPIは本体に統合されているため普段意識することは少ないかもしれませんが、CSSセレクタから要素を取得する場合はすべてこのセレクタAPIを経由して要素を取得します。

例えば以下のようなコードの場合、最大で4回セレクタAPIが呼び出されます。

$('#hoge') // 1
    .find('.huga') // 2
    .find('div') // 3
    .find('[rel="hoge"]') // 4
;

CSSセレクタの記述によって呼び出されるAPIの種類

jQueryのCSSセレクタ処理は大きく分けてブラウザネイティブAPIを使う場合と、JavaScriptで記述されたSizzleを使用する場合に分けられます。

querySelectorAllが使えるブラウザ(Chrome 1, Firefox 3.5, IE8, Safari 3.5, Opera 10、それぞれこれ以降のバージョン)では渡された引数が文字列かつセレクタとして認識できる形式の場合以下のような優先度で処理を行います。

  1. #idや.class等の簡単な記述は専用の高速なAPIを使用
  2. (1)以外でquerySelectorAllで解釈してエラーにならない場合その結果を使用
  3. (2)でエラーになる記述はSizzleを使用(非標準の記述が入っているなどの場合)

これはquerySelectorAllが使えるブラウザの例ですが、querySelectorAllが使えないブラウザでは2の処理がなくなり#idの場合とSizzleの場合にわかれます。

querySelectorAllとは

querySelectorAllとはブラウザ内蔵のCSSセレクタAPIで、jQueryのfindのように第一引数にCSSセレクタを渡すことでCSSセレクタに一致する要素を配列で返します。

// 「#hoge .huga」に一致する要素の配列
document.querySelectorAll('#hoge .huga');

これはjQuery内部でも使われており、複雑な記述のCSSセレクタは主にこのAPIで処理されます。

querySelectorAllで解釈できない記述

jQueryはCSSセレクタとして通常ブラウザが解釈できるCSSセレクタの他に、以下のようなjQueryが独自にサポートするCSSセレクタの記述を持ちます。

:animated, [name!="value"], :button,
:checkbox, :contains(), :eq(), :even,
:file, :first, :gt(), :has(), :header,
:hidden, :image, :input, :last, :lt(),
:odd, :parent, :password, :radio, :reset,
:selected, :submit, :text, :visible

これに関してはquerySelectorAllが解釈できないため、たとえquerySelectorAllが使用できるブラウザであっても必ずjQuery内部のSizzleを使用して解釈されます。

Sizzleとは

SizzleとはもともとquerySelectorAllがサポートされないブラウザ用にjQuery内部で使用されていたセレクタAPIを切り出したもので、CSSセレクタを受け取ってそれに一致する要素を返すライブラリです。

実際には高速化等の目的で他にもいろいろなコードが入っていますが、簡単に言うとgetElementsByTagName('*')で取得した要素一つ一つに対して、指定されたCSSセレクタに一致するかをJavaScriptで確認し、一致したものを配列にして返すようになっています。

このように内部がJavaScriptで記述されているため、独自の記述等をサポートすることが可能ですが、ブラウザネイティブのAPIに比べて速度的に遅くなるという問題があります。

高速なセレクタAPIを使用するための方法

ここまでセレクタAPIの内部実装に関して紹介してきましたが、ここからはそれを踏まえてjQueryがより高速なAPIを使用できる使い方を紹介します。

高速なセレクタと低速なセレクタでAPIの実行を分ける

jQueryの各APIは引数として渡されたCSSセレクタ全体がquerySelectorAllで実行可能かどうか判定してquerySelectorAllを使うかSizzleを使うかの分岐を行うので、渡されたCSSセレクタのうち一箇所でもカスタムセレクタが含まれていると全体をSizzleで評価します。

このため、CSSセレクタのうち一部分だけカスタムセレクタを使用する場合、その部分のみAPIを変えて検索することで高速化することが可能です。

// カスタムセレクタが含まれるため、全体がSizzleで評価される 
$('#hoge .huga:checked');

// ここはdocument.querySelectorAllで実行される
$('#hoge .huga')
    // ここはSizzleで評価される
    .filter(':checked');

また、querySelectorAllが使えない環境も考慮すると、以下のようにidとclassを分けることで高速化する場合もあります。
(ただ、APIを分けすぎるとAPI呼び出し自体のコストやコードの可読性にも影響するため、やりすぎると逆効果になる場合もあります)

// これはdocument.getElementByIdで評価される
$('#hoge')
    // これはブラウザによってはSizzleで評価される
    .find('.huga');

ショートカットを活用する

jQueryは基本的にquerySelectorAllを使用して要素を取得しますが、一部の記述にはより高速なAPIを使用して要素を取得します。

具体的には$('#hoge')や$('.huga')だけの場合はquerySelectorAllではなく、それぞれより高速なgetElementById、getElementsByClassNameを使用するようになっています。

このうち、getElementByIdはjQueryでサポートしている全ブラウザで使用可能なため$('#hoge')形式の場合は常にgetElementByIdが使用されますが、getElementsByClassNameは使用できるブラウザが少ないため、あくまでもquerySelectorAllが使用できる場合のみの高速な代替としてのみ使用されます。

このため、以下のような高速化が可能になります。

// document.querySelectorAllで解釈される(低速)
$('div#hoge');
// document.getElementByIdで解釈される(高速)
$('#hoge');

// document.querySelectorAllで解釈される(低速)
$('div.huga');
// document.getElementsByClassNameで解釈される(高速)
$('.huga');

jQueryセレクタAPIのベンチマーク - jsdo.it - share JavaScript, HTML5 and CSS

ただし、後者のclass名で指定する例に関して、IE6,7などのgetElementsByClassNameが使用できない環境でSizzleを使用して解釈するため以下のように高速な場合と低速な場合が逆転します。

// document.getElementsByTagName('div')後、クラスの比較を行う(高速)
$('div.huga');
// document.getElementsByTagName('*')後、クラスの比較を行う(低速)
$('.huga');

高速なセレクタAPIが使われるかどうか確認する

「あるCSSセレクタがどの程度の速度で実行されるか」は、実行されるブラウザ、実際のDOM環境等によって左右されるため一概に判断はできません。

ただ、基本的にSizzleはquerySelectorAllより遅いため、「あるCSSセレクタがSizzleを使って解釈されるか否か」は速度に関する一つの指標となります。

「あるCSSセレクタがSizzleを使って解釈されるか否か」はquerySelectorAllを使って解釈できるかどうか(エラーが出るかどうか)で判断できるため、「querySelectorAllではエラーになるけど、jQuery()に渡すと結果が返ってくる」CSSセレクタはSizzleで解釈されていると判断できます。

// jQueryのカスタムセレクタなのでエラー 
document.querySelectorAll(':input');
// jQueryでは解釈できるためSizzleが使用されている 
$(':input');

この方法でSizzleが使用される範囲を限定していくことで、querySelectorAllを活用しつつ、jQueryカスタムセレクタの柔軟な記述も行うことができます。

カヤックではAndroidと戦う技術者も募集しています!

こんにちは。すぎゃーん(@sugyan)です。

先日、Nakamap APIを公開しましたが、今回はそれをJavaScriptから使うためのライブラリを用意してみましたので紹介させていただきます。

使い方

<script type="text/javascript" src="http://developer.nakamap.com/js/nakamap.js"></script>

とscriptを読み込むことで使用できます。 access_tokenが既に取得できていれば、

var client = new Nakamap.Client({ token: '*********' });
/* REST API */
client.get('/me', function (result) {
    console.log(result);
});
/* Streaming API */
client.listen('/group/' + group_id, function (data) {
    console.log(data);
});

のような形でREST APIへのアクセス、Streaming APIからの受信ができます。

access_tokenはOAuth認証で取得しますが、Twitter @anywhereを参考に、access_tokenを意識せずに認証とAPIアクセスを行うインタフェースも用意しました。

<script type="text/javascript">
  Nakamap.anywhere({
      id: '********************'
  }, function (n) {
      if (n.isConnected()) {
          n.get('/me', function (result) {
              console.log(result);
          });
      } else {
          n('#nakamap').connectButton({
              scope: ['read_basic'],
              authComplete: function () {
                  n.get('/me', function (result) {
                      console.log(result);
                  });
              }
          });
      }
  });
</script>
<div id="nakamap"></div>

必要なのは開発者サイトでアプリ登録した際に発行されるclient_idのみです。 ここで登録したドメインと同一ドメイン上であれば動作します。 指定した要素にconnectButtonメソッドを呼ぶことで認証ダイアログをpopupさせるボタンが表示され、認証が完了すればその場でAPIへのアクセスが可能になります。

デモ

jsdo.itにてデモアプリを公開しています。JavaScriptのみで動く、Nakamapのチャットアプリです。

Nakamap App - jsdo.it - share JavaScript, HTML5 and CSS

動作原理

Nakamap APIのエンドポイントのドメインは"https://thanks.nakamap.com"となっており、JavaScriptからのアクセスは基本的にクロスドメインの通信をすることになります。 XMLHttpRequest Level 2であればAccess-Control-Allow-Originヘッダを指定することでクロスドメインの通信が可能になるので、Chrome, Firefox, Safariなどのブラウザではこれを使用します。

一方、Internet ExplorerではXDomainRequestを使用することで同様にクロスドメイン通信が可能ですが、こちらはhttp/httpsの壁が越えられないなどの制約もあり、IE8以降を対象にwindow.postMessageを使ったAPIアクセスを行うようにしています。 具体的には、https://thanks.nakamap.com/上にレシーバとなるページを用意し、iframeでそのページを読み込み、window.postMessageを使ってリクエストとレスポンスをJSONでシリアライズした文字列を使ってやりとりする、という方法です。 レシーバのページからのAPIアクセスは同一ドメインからのアクセスになるため、通常のXMLHttpRequestで取得できます。 window.postMessageではiframeの親と子の間でメッセージをやり取りできます。これを使って別ドメインからのリクエストをここで中継して代わりに行い、結果を返す、ということでInternet Explorerでもクロスドメインのリクエストを実現させます。

また、Streaming APIの受信ですが、XMLHttpRequest Level 2ではmultipart/mixedなレスポンスを受けるmxhr.jsというライブラリがありますのでこれを使わせていただいています。 Internet ExplorerのXMLHttpRequestではレスポンスを途中まで受け取って処理する、ということができませんが、XDomainRequestが近い機能を持っています。 ただし、最初の数百バイトは受け取ってもonprogressイベントは発火されない仕様のようで、最初にダミーデータとして数百バイトのデータを返すようにしました(XDomainRequest with "prelude"として紹介されています)。 これを使うことで、IE8以上の環境でもStreaming APIからのデータを受信できるようにしています。

まとめ

JavaScriptは様々な環境での動作を考慮しないといけないので大変ですね。。 たくさん躓いて色々と勉強になりました。

 

カヤックではNakamapとJavaScriptが大好きな技術者も募集しています!

10/15 開催!! まだお申し込み受け付けております。みなさまのご参加お待ちしております!

イベントの詳細

面白法人カヤックでは、定番のtwitterアプリをiPhone・Androidの両方で改造しながら学ぶワークショップイベントを開催します。

当日は、その場でチームを編成し、twitterアプリを課題に応じてオリジナルにアレンジ・改造していきます。 課題は、当日その場で発表! 限られた時間の中でコードを書き、最後にチームごとに制作物を発表する対抗戦です。 スマフォ開発初心者の方でもまずは挑戦したいという気持ちさえあれば当日お楽しみいただけるかと思います。 ※参加資格のご確認をお願いします。

チーム編成はご参加の方のご希望に応じ、iPhone開発チーム、Android開発チームをそれぞれ3?4名のチームにわかれての開発となる予定です。

制作を開始する前には、アプリ制作に従事している弊社社員から、 課題の開発についてのワンポイントアドバイスなどを30分程で、解説をさせていただきます。

また、当日は日々スマートフォンアプリ開発を行っているカヤックのプログラマもチームの一員として参加させていただきます!

業務ではなかなか取り組めないテーマを、いつもとは違うメンバーで開発できる楽しい場にできればと考えております。奮ってご参加ください。

日時

  • 10/15(土)10:00-18:00
    ※ご希望の方のみ:18:00-19:30で懇親会を予定しております。
    ※受付は9:40から開始いたします。
  • 10:00-10:30 課題発表・チーム分け・ワンポイントアドバイス
  • 10:30-17:00 チーム毎にアイデア出し・開発 ※こちらの時間内にチーム毎にお昼休憩は自由にとっていただきます。
  • 17:00-18:00 各チームの発表
  • 18:00-19:30 懇親会 ※参加はご希望の方のみ

参加対象

  • 実機に転送できることが本ワークショップの対象となります。
  • 勉強会の特性上、今回はエンジニアの方限定とさせていただきます。ご了承ください!

ご参加にあたっての注意点

  • 開発環境の構築サポートは行いません。ワークショップ当日までに予め開発環境の構築をお願い致します。
  • 当日、検証端末の貸し出しは行いません。
  • 昼食はこちらでご用意致しません。予めご了承ください。
  • ワークショップ終了後、懇親会を予定しております。参加費は2000円となります。ご都合よろしい方はぜひふるってご参加ください。

会場側の備品

  • ネット環境
  • 電源タップ

場所:カヤック 恵比寿支社

http://www.kayac.com/company/map#ebisu

定員:25名

※会場の都合で、応募多数の場合は、参加者を制限させて頂く場合がございます。以下のフォームより、お申込みください。
http://p.tl/cu5f