はじめに
jQueryとは、John Resigによって開発され、最近非常に注目を集めている Javascriptライブラリです。 JavaScriptとHTMLの対話を劇的に改善し、Ajaxなどにより複雑化してきているWebアプリケーション構築に必要な処理を非常に簡潔に書くことができます。また、ブラウザの種類やバージョンによる違いも吸収してくれるため、プログラマの作業量も減らしてくれます。
本連載では、jQueryライブラリのコードを読みながら、実装として中で何が行われているのかを見ていこうと思います。
想定している読者は、jQueryライブラリをただ使うだけでなく、やっていることを理解したいという方、使われているコードを応用して自分なりの改造をしてみたい方、新たなプラグインを開発したいという方などです。
今回の連載では執筆開始時点の最新版であるバージョン1.2.2を対象としています。説明の都合上行番号に大きく依存しているため、最後までバージョン1.2.2を対象とすることになります。
jQuery ソースコードの入手
jQueryは、jquery-1.2.2.jsという数十KBの一つのファイルにすべて書かれています。このソースコードは、公式サイトからダウンロードできます。圧縮方法の違いによって3つのバージョンがあり、実際のサイトで利用するにはMinified版がお勧めなのですが、ソースコードの解説なのでここではUncompressed版を利用します。ぜひ、お手元にダウンロードして記事と一緒にご覧になってください。
Uncompressed |
93KB |
通常のソースコード(無圧縮) |
Minified and Gzipped |
51KB |
コメントや不要な空白を取り除いたもの |
Packed |
28KB |
Packerによって圧縮したもの |
ローカルスコープ化
jQueryの良いところは、名前空間を汚染しないところです。すべての機能をロードした状態で、jQueryと$の2つのオブジェクトしか存在しません。このため、他のJavaScriptライブラリと混ぜて使うことができます。これを実現しているのが、ソースコードの最初に登場するこの部分です。
全体のコードが、(function(){...})() で囲まれています。初めて見ると不思議な感じがしますが、無名関数を定義してすぐに実行するという処理を行っています。関数は、ふつうhoge()のような形で呼び出しますが、最初の()で括られた部分がhogeにあたり、その後に引数なしの()が続くと説明すれば少し分かりやすいでしょうか。
これにより、すべての定義をローカルスコープに押し込めることに成功しています。
先頭のコメント, 著作権表示
MITライセンスとGPLのデュアルライセンスで公開されています。あなたがパフォーマンスのためにスクリプトを圧縮したとしても、このクレジット表示は削除しないように注意しましょう。jQueryは、自ら"New Wave Javascript"と名乗るだけのことはあり、非常に興味深いライブラリです。深く読み進めるにつれて、このことが段々明らかになってくることでしょう。
初期化処理
14行目では、既にwindow.jQueryオブジェクトが存在するときは、_jQueryに一時的に格納しておきます。これは後で、noConflict()を呼び出したときに元の状態に戻すためです。
次に出てくるコードは jQueryの初期化の部分になります。522行目のコードも一緒に見た方が分かりやすいので、順番は前後してしまいますが、ここで一緒に説明することにします。
17行目がまさにjQueryの根幹となる部分で、ここでjQueryオブジェクトを定義しています。jQueryオブジェクトの実体はinitメソッドによるコンストラクタで、オブジェクトが生成された際にinit()メソッドを実行してインスタンスを返します。よって、jquery.jsのロード時には定義が行われるのみで、jQuery関数(もしくは$)が呼ばれて初めて jQueryのプロトタイプオブジェクトのinit()が実行され、処理が行われます。
また、523行目ではjQuery.prototype.init関数オブジェクトのプロトタイプにjQuery関数オブジェクトのプロトタイプを設定しています。どういうことかというと、jQuery.prototypeに定義された様々なメソッドやプロパティを継承することで、jQueryライブラリの特徴であるメソッドチェーンを実現しているのです。
この辺りのコードは1.2.2で大幅に書き換えられました。リリースノートによると、ここを改善することで$(DOMElement)の処理を3倍高速化できたそうです(38ms → 13ms)。
※この部分を理解するにはJavaScriptのプロトタイプ指向に関する知識が必要となるのですが、とても興味深い部分ですので、余裕があればECMAScriptの仕様書等を読んでみることをお勧めします。
14行目と同様に、既にwindow.$オブジェクトが存在するときは、_$に一時的に格納しておきます。また、27行目でjQueryのショートカットを$に割り当てます。
パラメータによって処理を分岐するための正規表現です。quickExprは、パラメータがHTML文字列か単純なID指定かを判定するために、isSimpleはシンプルな選択の場合にパフォーマンスを最適化するための分岐用として利用されます。
initメソッド定義
36行目からが jQueryオブジェクトの初期化処理になります。jQuery.fnとjQuery.prototypeに対してメソッドを定義していきます。
37行目で定義されているinit関数がjQueryオブジェクトの事実上のコンストラクタになります。39行目の文により、$()のように何も引数を渡されずに呼び出された場合は、$(document)と同義になります。
引数にDOMエレメントが渡された場合の処理です。渡されたDOMエレメントを格納し、lenghtに1を設定してそのままjQueryオブジェクトを返します。
引数に文字列が渡された場合で、それがHTML文字列かつコンテキストが指定されていない場合は、cleanメソッドを実行してselectorに設定し直しています。cleanメソッドについては次回以降で説明しますが、不正なHTML表記をきれいにしてDOM要素を構築していると思ってください。
単純なID指定の場合には、getElementByIdでそのIDをもつ要素を検索し、みつかればその要素を配列に格納し、lengthに1を設定してそのまま返します。ただし、IEとOperaの場合は戻り値の形式が異なるために自身のfindメソッドを呼び出します。指定されたIDがみつからなかった場合は、selectorを空にします。
contextが指定されている場合の処理です。一旦contextについてのjQueryオブジェクトを生成してから、findメソッドを実行します。82行目のコメントにもある通り、contextを指定した処理は、$(content).find(expr) と同義であることが分かります。
引数として関数が渡された場合、つまり $(function(){ ... }) のように呼び出された場合の処理です。これは、$(document).ready(function(){ ... }); と一緒でDOMツリーの構築を待ってから指定された関数を実行します。
上記のいずれでもない場合の処理です。もし、配列やjQueryオブジェクト、DOM要素の配列が渡されていたら、その配列で初期化して返します
バージョン
104行目は、バージョン情報を定義しています。
size()
length と size は、それぞれ要素の個数とその個数を返す関数です。
get()
116行目は、get(n)でn番目の要素を取得する関数を定義しています。もし、引数nが渡されなかった場合は、makeArray関数(次回以降で説明します)によって返されるクリーンな配列を返します。
pushStack()
便利なスタック機能を実現している部分です。新しいjQueryオブジェクトのprevObjectというプロパティに現在のオブジェクトを格納して返します。
endメソッドが呼ばれた場合に、この格納しておいたprevObjectが使われます。
setArray()
142行目のsetArrayメソッドは、選択された要素の強制的に初期化します。lengthに0をセットし、JavaScript標準のArray pushを使って初期化します。コメントにもあるようにスタックに格納していたオブジェクトも破棄されます。
each()
154行目にて定義されているeachメソッドは、JavaScriptでいわゆるイテレータを実現するためのものです。選択された要素に対して、順番にcallback関数を適用していきます。ここは紛らわしいのですが、別の場所(709行目)で定義されているjQuery.eachメソッドを実行します。なお、第2引数のargsは内部的に利用するためのもので、ドキュメントにも書かれておらず外部からは利用するべきではありません。
index()
選択された要素の中で、ある要素が何番目に出てくるのかを返す関数です。先ほど出てきたeachメソッドを利用して、ある要素が見つかればそのインデックスを、みつからなければ-1を返します。
いかがだったでしょうか。第1回はjQueryを理解する上で重要な処理が多かったため、最初の171行までしか説明できませんでした。しかし、ここまで見ただけでもとても興味深いライブラリだということが分かっていただけたのではないでしょうか。
それでは、次回は172行目以降を説明していきます。お楽しみに!