- $.fnを理解するとjQueryの正体が見える
- prototypeとは何か
- なぜ$("div").hide()が呼べるのか
- $.fnに追加すると何が起きるか
- thisが指しているもの
- なぜ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の仕組みを「誰でも使える形」にしたライブラリでした。
その設計こそが、長く使われ続けた本当の理由と言えるかもしれません。