jQueryプラグインの基本構造をコードで理解する

jQueryプラグインは「特別な仕組み」ではない

jQueryプラグインという言葉を聞くと、何か専用のAPIや複雑な仕組みがあるように感じるかもしれません。しかし実際には、jQueryプラグインは驚くほど単純です。
結論から言うと、jQueryプラグインとは「jQueryオブジェクトのメソッドを1つ追加しているだけ」です。

特別な登録処理も、設定ファイルも、ビルドも必要ありません。JavaScriptのオブジェクトとprototypeの理解さえあれば作れてしまいます。

この「簡単すぎる拡張方法」が、jQueryプラグイン文化を爆発的に広げました。なぜなら、アプリケーション開発者でも簡単にライブラリ作者になれてしまったからです。

最小のjQueryプラグインを書いてみる

まずは、最も小さなjQueryプラグインを書いてみます。

(function($){
  $.fn.red = function(){
    return this.css("color", "red");
  };
})(jQuery);

これだけです。

このコードを読み込んだページでは、次のように書けます。

$("p").red();

すべての<p>要素の文字色が赤になります。
ここで重要なのは、「新しい関数を作った」のではなく、jQueryオブジェクトにメソッドが追加されたという点です。

$.fnとは何か

プラグインの核心が「$.fn」です。

$.fnは、jQueryオブジェクトのprototypeを指しています。つまり、ここに関数を追加すると、$("div")のように生成されるすべてのjQueryオブジェクトがその関数を使えるようになります。

イメージとしては次の関係です。

追加場所 結果
window グローバル関数になる
$.fn jQueryのメソッドになる

つまり、

$.fn.sample = function(){};

と書いた瞬間に

$("div").sample();

が呼べるようになります。これがjQuery拡張の正体です。

なぜ(function($){ })(jQuery)で囲むのか

ほとんどのjQueryプラグインは、次の形をしています。

(function($){
  // plugin
})(jQuery);

これは単なるお作法ではありません。明確な理由があります。

昔はPrototype.jsなどのライブラリも「$」を使用していました。複数のライブラリを同時に読み込むと、$が上書きされる事故が頻発しました。

この書き方をすると、関数の引数としてjQueryが渡され、内部では安全に$を使えます。
つまり、外の世界に影響されない「隔離された$」を作っているのです。

return this が必要な理由

jQueryプラグインを書くとき、必ず出てくるのがこの一文です。

return this;

なぜ必要なのでしょうか。

jQueryの特徴の1つがメソッドチェーンです。

$("p")
  .hide()
  .css("color","red")
  .fadeIn();

もしプラグインがthisを返さなければ、ここでチェーンが途切れます。
たとえば次のプラグインは間違いです。

$.fn.bad = function(){
  this.css("color","red");
};

これを使うと

$("p").bad().hide();

はエラーになります。bad()の戻り値がundefinedだからです。

そのため、jQueryプラグインは必ずjQueryオブジェクトを返します。
つまり、プラグインは「処理」ではなく「操作の一部」として振る舞う必要があるのです。

eachを使う理由

多くのサンプルで次の書き方を見かけます。

$.fn.example = function(){
  return this.each(function(){
    $(this).css("border","1px solid red");
  });
};

なぜわざわざeachを使うのでしょうか。

$("div")は1つの要素ではなく「複数要素の集合」です。
もしeachを使わずにDOM操作をすると、最初の1件しか処理されないことがあります。

つまりeachは「すべての要素に同じ処理を適用する」ための基本パターンです。
この構造を守らないプラグインは、実際のサイトで簡単に壊れます。

オプションを受け取るプラグイン

実用的なプラグインは設定を受け取ります。

(function($){

  $.fn.highlight = function(options){

    var settings = $.extend({
      color: "yellow",
      speed: 200
    }, options);

    return this.each(function(){
      $(this).css("background-color", settings.color)
             .fadeIn(settings.speed);
    });
  };

})(jQuery);

使用例です。

$("p").highlight({ color: "pink", speed: 500 });

$.extendは、デフォルト設定とユーザー設定を合成します。
これにより、引数を省略しても動作し、必要な場合だけ上書きできます。多くのjQueryプラグインがこの形を採用していました。

実際に起きやすかったトラブル

現場で最も多かった問題は「再初期化」です。

同じ要素に対して、Ajaxの再描画などでプラグインを再適用すると、イベントが重複登録されます。クリック1回で2回処理される現象の原因の多くがこれでした。

防ぐには、初期化済みフラグを持たせます。

if($(this).data("initialized")) return;
$(this).data("initialized", true);

小さな工夫ですが、実際の運用では非常に重要でした。

最後に

jQueryプラグインは難しい技術ではありません。むしろ、JavaScriptの基本的な仕組みだけで成立しています。
しかし、だからこそ多くの開発者が参加でき、巨大なエコシステムが生まれました。

現在はnpmやフレームワークが主流ですが、「小さく拡張可能なコア」という設計思想は今でも変わっていません。
jQueryプラグインを理解すると、現代のJavaScriptライブラリがなぜあの構造になっているのか、かなりはっきり見えてきます。