こんにちは、ほそ道です。
今回はjQuery拡張の仕組みについて掘り下げます。
下記のようなメンバ変数へのアクセスってどのように実現しているのでしょうか?
jQuery.xxx()
jQuery('x').xxx()
対象バージョン:jQuery-2.1.1
目次はこちら
jQueryのガチなコア
いきなりですが、ほそ道的には今回の研究の最大の成果です。
jQueryを本当に骨と皮だけにするとこうなります。ババーン!
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
jQuery.fn = jQuery.prototype = {};
init = jQuery.fn.init = function( selector, context ) {};
init.prototype = jQuery.fn;
関数やオブジェクトの中身は空にしていますが、これは実際のjQueryソースの構文を抽出したもので、これだけでも問題なく動作します。
それでは内容をチェックしていきましょう。
jQuery関数
ひとつ目の実行文から見ていきます。
jQuery関数は実行されると、自分が受け取った同じ引数をそのままnew jQuery.fn.init
に渡して実行させています。
jQuery.fn.init関数はjQueryインスタンスを生成して返しますので
「jQuery関数とはjQueryインスタンスを生成する為のコンストラクタである」
ということが言えると思います。
init関数
次に三番目の実行文を見てみます。
jQuery関数から呼ばれるjQuery.fn.init関数はinit
という変数に代入されます。
init関数はjQueryインスタンスの内部的なコンストラクタということになります。
※jQuery関数はさしずめ外向きのコンストラクタといった所でしょうか。
jQuery.fnオブジェクト
二番目と四番目の実行文を見ると、二つの文をまたがりながら超重要な代入が行われています。
二つの文をマージすると下記のようになってます。
init.prototype = jQuery.fn = jQuery.prototype = {};
重要なポイントは下記です。
jQuery.fnというオブジェクトがinit.prototypeに代入されている事。
つまりは「すべてのjQueryインスタンスはjQuery.fnオブジェクトのメンバを継承する」という事です。
ちなみにnew jQuery(selector)
とするようなシチュエーションは思いつかなかったのでjQuery.prototype
の部分は余り重要ではないのではないかと思っています。
jQuery.fnによる拡張を試す
それではjQuery.fnによる機能拡張を実証してみましょう。
問題なく動いてます。
jQuery.fn.foo
で関数がセットされ、jQueryインスタンスからの呼び出し$().foo()
に成功しました。
ちなみにprototype継承を行っているので$.foo()
はズッコケます。
この場合はjQueryに直接メンバを突っ込む事で拡張が出来ます。
jQuery.fn.extend関数による拡張
さて、jQuery.fnによる機能拡張が出来る事がわかりましたが、
jQueryは機能拡張についてjQuery.fn.extendというAPIを提供しています。
このようなやり方でもうまく拡張できる事が解ります。
ちなみにjQuery.extendとやる事でjQuery関数のメンバを設定する事も出来ます。
どっちの拡張方式を使うべきか?
さて、2つの拡張方式を見ていきました。
jQuery.fnに突っ込む方式とjQuery.fn.extend関数を使う方式です。
どっちを使うべきかと問われれば「どっちでも良いですよ」というのがファイナルアンサーですかね。。
オブジェクトを設定するときはjQuery.fn.extend関数を使うべきかと思います。
対して単一の関数を設定するときはjQuery.fnに設定しても良いと思います。
それでは実際の機能拡張はどのように実装されているのかを見ていきます。
実際の拡張実装を見てみる
イベントの実装
早速いきましょう。jQueryのイベントの実装は下記のようになってます。
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
// Handle event binding
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
});
イベント関数の定義は、jQuery.fn
オブジェクトの拡張によって実現していますね。
Ajaxの実装
続いてAjaxの拡張実装です。
jQuery.extend({
/* 設定周りのコード... */
ajax: function( url, options ) { /*...*/ },
getJSON: function( url, data, callback ) { /*...*/ },
getScript: function( url, callback ) { /*...*/ }
});
こちらはインスタンスに依存せず、$.ajax
などと使用されるのでjQuery.extendを使用しています。
プラグインの実装
画像をお洒落にサムネイル+拡大表示してくれるleast.jsさんを例に見てみます。
githubのjsコードを拝見いたします。
(function($){
$.fn.least = function(options) { /.../ };
})(jQuery);
こちらはjQuery.fnで設定を行っています。
自分でプラグインを作るときのお作法としてどのやり方で拡張していくのが良いか考えるのも楽しそうです。
今回は以上です。
jQueryのコアが理解できたので、その先はいろんな機能がそこに拡張されていっている事がイメージ出来るようになりました。
次回からは 少しjQueryに飽きてきたjQueryの個々の処理を深掘るよりも、そのコードから得られるエッセンスからJSの基本的な処理の深掘りに移ります。
とりあえずcall/apply関数を取り上げます。