Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

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

今回は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による機能拡張を実証してみましょう。

スクリーンショット 2014-06-22 9.54.11.png

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

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

スクリーンショット 2014-06-22 9.55.51.png

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

さて、jQuery.fnによる機能拡張が出来る事がわかりましたが、
jQueryは機能拡張についてjQuery.fn.extendというAPIを提供しています。

スクリーンショット 2014-06-22 9.55.51.png

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

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

スクリーンショット 2014-06-22 9.52.10.png

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

さて、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の拡張実装です。

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

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

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

プラグインの実装

画像をお洒落にサムネイル+拡大表示してくれるleast.jsさんを例に見てみます。
githubのjsコードを拝見いたします。

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

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

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

hosomichi
粋になりたい
fringe81
Fringeは、最新のテクノロジーとプロフェッショナルによるサービスにより、社会課題に仮説を立てて市場に広げていくことで、数十年という長期的なスパンで価値を生み出し続け、より良い世界を創る集団です。 既存の領域に限らず、時流を読み、仮説を生み出し、テクノロジーの力で優れたサービスを生み出し続けます。
https://www.fringe81.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away