$.fnの意味とjQuery拡張の仕組みを理解する

$.fnを理解するとjQueryの正体が見える

jQueryを長く使っていても、$.fnをきちんと理解している人は意外と多くありません。
しかしここを理解すると、jQueryは「魔法のライブラリ」ではなく、JavaScriptのprototypeを非常に上手く利用した設計であることが分かります。

まず重要な点から言います。
$.fnは特別な機能ではありません。内部的には単なる別名です。

実はjQueryのソースコードには次のような定義があります。

jQuery.fn = jQuery.prototype;

つまり、$.fnとは「jQueryオブジェクトのprototypeそのもの」です。

prototypeとは何か

JavaScriptのオブジェクトは、クラスのように見えて実はprototypeベースです。
あるオブジェクトに存在しないプロパティを参照すると、prototypeチェーンをたどって探しに行きます。

例を見てみます。

function Person(name){
  this.name = name;
}

Person.prototype.hello = function(){
  console.log("hello " + this.name);
};

var p = new Person("Taro");
p.hello();

helloメソッドはインスタンスに存在していません。しかしprototypeにあるため呼び出せます。

jQueryも同じ仕組みです。$("div")が返すオブジェクトは、内部的にjQuery.prototypeを参照しています。
だから$.fnにメソッドを追加すると、すべてのjQueryオブジェクトから使えるようになります。

なぜ$("div").hide()が呼べるのか

次のコードは当たり前のように書いています。

$("div").hide();

しかしよく考えると不思議です。$("div")はDOM要素ではありません。配列でもありません。それなのにhide()が呼べます。

理由は、hide()がjQuery.prototypeに定義されているからです。

イメージはこうです。

処理 実際に起きていること
$("div") jQueryオブジェクトを生成
.hide() prototypeのメソッドを呼び出す

つまり、jQueryは「DOMを操作する関数群」ではなく、DOMを包んだラッパーオブジェクトなのです。

$.fnに追加すると何が起きるか

ここで次のコードを書きます。

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

この瞬間、jQueryオブジェクトのprototypeにメソッドが追加されます。
結果として、すべてのjQueryオブジェクトがblueを持つようになります。

$("p").blue();
$(".menu li").blue();
$("#header").blue();

特別な登録処理は一切ありません。prototypeに追加しただけです。
つまり、jQueryプラグインとはprototype拡張そのものです。

thisが指しているもの

プラグインを書くときに混乱しやすいのがthisです。
$.fn内のthisは、DOM要素ではありません。jQueryオブジェクトです。

$.fn.test = function(){
  console.log(this);
};

このthisの中身は、選択された要素の集合です。

そのため、次のコードは間違いです。

this.style.color = "red";

これはDOM要素に対する書き方です。正しくは、

this.css("color","red");

になります。

もしDOM要素を直接扱いたい場合はeachを使います。

$.fn.sample = function(){
  return this.each(function(){
    this.style.color = "red";
  });
};

each内のthisはDOM要素になります。ここはjQuery初心者が最も混乱するポイントでした。

なぜjQueryはこの設計を選んだのか

当時のJavaScriptライブラリには2つの流派がありました。

1つは「関数を提供する」タイプ。
もう1つは「オブジェクトを返す」タイプです。

jQueryは後者を選びました。この設計の利点は明確です。

  • メソッドチェーンができる
  • 拡張が容易
  • 状態を保持できる

特に3つ目が重要です。
jQueryオブジェクトは、選択されたDOM集合という「状態」を持ちます。その状態に対して操作を連続適用できるため、コードが短くなりました。

メソッドチェーンが成立する理由

次のコードが成立する理由も、prototype設計です。

$("p")
  .addClass("active")
  .fadeOut()
  .fadeIn();

各メソッドがthis(jQueryオブジェクト)を返すため、次のメソッドが呼べます。
もしhide()がtrueやundefinedを返していたら、この書き方はできません。

つまり、jQueryの書きやすさはprototypeとreturn thisの組み合わせによって作られているのです。

拡張の危険性

便利な反面、prototype拡張にはリスクもありました。
複数のプラグインが同じ名前のメソッドを追加すると、後から読み込んだ方が上書きします。

例えば、

  • tooltipプラグインA
  • tooltipプラグインB

の両方が$.fn.tooltipを定義すると、片方は動かなくなります。しかもエラーにならず静かに壊れます。

これはjQueryの欠点ではなく、prototype拡張の宿命でした。
そのため、大規模開発では名前空間を付ける慣習が生まれました。

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

最後に

$.fnは難しい概念ではありません。
jQueryを特別な技術だと感じていた理由の多くは、prototypeというJavaScriptの基礎が隠れていたからです。

jQueryは独自の仕組みを発明したのではなく、既存のJavaScriptの仕組みを「誰でも使える形」にしたライブラリでした。
その設計こそが、長く使われ続けた本当の理由と言えるかもしれません。