jQuery拡張の仕組み 〜 JSおくのほそ道 #013

こんにちは、ほそ道です。

今回はjQuery拡張の仕組みについて掘り下げます。
下記のようなメンバ変数へのアクセスってどのように実現しているのでしょうか？

```js
jQuery.xxx()
jQuery('x').xxx()
```

対象バージョン：jQuery-2.1.1
[目次はこちら](http://qiita.com/setzz/items/ad9a9c99a4fda041a563)

# jQueryのガチなコア

いきなりですが、ほそ道的には今回の研究の最大の成果です。
jQueryを本当に骨と皮だけにするとこうなります。ババーン！

```js:ガチコア
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オブジェクト
二番目と四番目の実行文を見ると、二つの文をまたがりながら超重要な代入が行われています。
二つの文をマージすると下記のようになってます。

```js:超基底オブジェクト
init.prototype = jQuery.fn = jQuery.prototype = {};
```

重要なポイントは下記です。

**jQuery.fnというオブジェクトがinit.prototypeに代入されている事。**

つまりは「すべてのjQueryインスタンスはjQuery.fnオブジェクトのメンバを継承する」という事です。
ちなみに`new jQuery(selector)`とするようなシチュエーションは思いつかなかったので`jQuery.prototype`の部分は余り重要ではないのではないかと思っています。

# jQuery.fnによる拡張を試す

それではjQuery.fnによる機能拡張を実証してみましょう。

![スクリーンショット 2014-06-22 9.54.11.png](https://qiita-image-store.s3.amazonaws.com/0/39330/82563322-2938-3cbf-54e9-1987e84c9a87.png)

問題なく動いてます。
`jQuery.fn.foo`で関数がセットされ、jQueryインスタンスからの呼び出し`$().foo()`に成功しました。

ちなみにprototype継承を行っているので`$.foo()`はズッコケます。
この場合はjQueryに直接メンバを突っ込む事で拡張が出来ます。

![スクリーンショット 2014-06-22 9.55.51.png](https://qiita-image-store.s3.amazonaws.com/0/39330/e83856c1-b0f1-9166-6cea-bf7a18e1c81a.png)

# jQuery.fn.extend関数による拡張

さて、jQuery.fnによる機能拡張が出来る事がわかりましたが、
jQueryは機能拡張について[jQuery.fn.extend](http://api.jquery.com/jQuery.fn.extend/)というAPIを提供しています。

![スクリーンショット 2014-06-22 9.55.51.png](https://qiita-image-store.s3.amazonaws.com/0/39330/d0eb63fd-5b72-1b90-c0b6-a80431202962.png)


このようなやり方でもうまく拡張できる事が解ります。

ちなみにjQuery.extendとやる事でjQuery関数のメンバを設定する事も出来ます。

![スクリーンショット 2014-06-22 9.52.10.png](https://qiita-image-store.s3.amazonaws.com/0/39330/7b2cc518-4028-f5ff-95f6-a897ce74790c.png)

# どっちの拡張方式を使うべきか？

さて、２つの拡張方式を見ていきました。
jQuery.fnに突っ込む方式とjQuery.fn.extend関数を使う方式です。
どっちを使うべきかと問われれば「どっちでも良いですよ」というのがファイナルアンサーですかね。。
オブジェクトを設定するときはjQuery.fn.extend関数を使うべきかと思います。
対して単一の関数を設定するときはjQuery.fnに設定しても良いと思います。
それでは実際の機能拡張はどのように実装されているのかを見ていきます。

# 実際の拡張実装を見てみる
## イベントの実装

早速いきましょう。jQueryのイベントの実装は下記のようになってます。

```js:イベントの実装
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の拡張実装です。

```js:ajaxの実装
jQuery.extend({
  /* 設定周りのコード... */

  ajax: function( url, options ) { /*...*/ },
  getJSON: function( url, data, callback ) { /*...*/ },
  getScript: function( url, callback ) { /*...*/ }
});
```

こちらはインスタンスに依存せず、`$.ajax`などと使用されるのでjQuery.extendを使用しています。

## プラグインの実装

画像をお洒落にサムネイル＋拡大表示してくれる[least.js](http://kamilczujowski.github.io/least/)さんを例に見てみます。
[githubのjsコード](https://github.com/kamilczujowski/least/blob/gh-pages/js/least.js)を拝見いたします。

```js:least.js
(function($){
  $.fn.least = function(options) { /.../ };
})(jQuery);
```

こちらはjQuery.fnで設定を行っています。
自分でプラグインを作るときのお作法としてどのやり方で拡張していくのが良いか考えるのも楽しそうです。

今回は以上です。
jQueryのコアが理解できたので、その先はいろんな機能がそこに拡張されていっている事がイメージ出来るようになりました。
次回からは ~~少しjQueryに飽きてきた~~jQueryの個々の処理を深掘るよりも、そのコードから得られるエッセンスからJSの基本的な処理の深掘りに移ります。
とりあえず[call/apply関数を取り上げます](http://qiita.com/setzz/items/155af03120a330fd4437)。