MooToolsの開発者がわざわざドメインを取ってまで書いていたので、全訳してみました。コードハイライターは自作なので、インデント付けがまだできてません。そこら辺が読みづらいのはご勘弁を。
jQuery vs MooTools
May, 2009 – Aaron Newton of Clientcide
こんにちJavaScriptをはじめる人は生半可ではない問題に直面する。どのライブラリを使用すべきか、そうでなければ少なくとも、どのライブラリから、学び始めるべきか、という問題である。もしあなたが企業に勤めており、その企業ですでに使用するフレームワークが決められているならば、論争の種となりそうな問題だ。もしこの場合にあてはまり、企業で使うのはMooToolsで、あなたがを使っているのがjQueryであるという具合でも、この記事は幾らか役立つだろう。
私はTwitterで「MooToolsか、jQueryか?」とつぶやくポストをま、い、に、ち目にしている。この記事の狙いはその選択の一助となることである。
免責事項
私はMooToolsディベロッパーである。MooToolsフレームワークのために働いている。MooToolsに関するブログを書いている。有名なオンラインチュートリアルとMooToolsの本を書いた。 明らかに私の意見には偏見が含まれている。また、jQueryをほとんど使わないということも明確にしておこう。もしあなたがjQueryディベロッパーであり、間違った表現を見つけたら、コンタクトを取って問題を訂す手助けをしてほしい。私の目的は読者の役に立ち、嘘を言わないことだ。あるフレームワークが他のものより優れていると主張することではない。
目的
これら二つのフレームワークから一つを選択するために、私はあなたに対して、その二つがどのように異なっているかを説明する。まずはじめに言っておきたいのが、どちらを選んでもすばらしい選択をしたことになるということだ。この場合、間違った選択をすることはない。どちらのフレームワークも一長一短があり、総合的に見れば、どちらも素晴らしい選択である。同じように詳しく述べる価値のあるフレームワークが他にもある。Dojo、Prototype、YUI、Extなどはすべて素晴らしい選択肢だ。どれを選んでも自分のやり方でできること、成し遂げられることはたくさんある。この記事の目的は、多くの人々が注目している二大フレームワークになりつつあるMooToolsとjQueryに焦点を当てることだ。最後につけくわえると、私はフレームワークを乗り移るよう説得したりはしない。この二つのフレームワークについては興味深い点が多くあり、そこから学ぶことも多い。この記事を書いた理由について、この記事の告知をしたClientcideの私のブログポストでも読むことができる。
目次
- モットーがすべてを語る
- 学習曲線とコミュニティ
- JavaScriptは何に向いているか
- MooToolsはJavaScriptそのものをもっと楽しくする
- jQueryはDOMをもっと楽しくする
- あなたのできることを私はもっとよくできる
- MooToolsはあなたにあなたの道を歩ませる
- デザインパターンとしての連鎖
- jQueryでのコード再利用
- MooToolsでのコード再利用
- 決断の時
数値比較
jQuery Core | MooTools Core | |
---|---|---|
ライブラリサイズ | 55.9K | 64.3K |
機能 | ||
ライセンス | MIT & GPL | MIT |
DOM 機能 | yes | yes |
アニメーション | yes | yes |
イベント操作 | yes | yes |
CSS3セレクタ | yes (サブセット) | yes (サブセット) |
Ajax | yes | yes |
ネイティブ拡張 (Elementを除く) | Array、Object、Stringに対して約1ダース | Array、Object、String、Function、Numberに対して約6ダース |
継承 | jQueryでは直接サポートされていない | Classコンストラクタによって提供されている |
その他 | ||
プラグイン | 何百もの非公式プラグインがplugins.jquery.comにある | 約4ダースの公式プラグインがmootools.net/moreにある。その他、Web上に多数。組織的なカタログはない。 |
オフィシャルUI ライブラリ | yes | no |
jquery.com、mootools.netとwikipedia.comからの情報による
モットーがすべてを語る
jQueryサイトに行けば、ページのトップ付近にjQueryとはなんであるかが述べられている:
jQueryは高速で簡潔なJavaScriptライブラリです。HTML文書の走査、イベント操作、アニメーション、Ajaxインタラクションをシンプルにして、Webサイト開発を高速にします。jQueryはJavaScriptの書き方を変えるために設計されました。
…そしてMooToolsに行けば、このような文言が見つかるだろう:
MooToolsは中級から上級のJavaScript開発者のために設計された軽量で、モジュール化された、オブジェクト志向のJavaScriptフレームワークです。これを使えば、エレガントで、きちんとした裏付けのある、一貫したAPIにより、パワフルに、柔軟に、そしてクロス・ブラウザなコードを書くことができます。
これはすばらしい要約だ。もしあなたが私に質問をしたら(これを読んだ、そのすぐ直後だと思うが)、その質問はフレームワークの善し悪しではないだろう。上の中のどれをしたいのか、ということだろう。この二つのフレームワークは同じ事をしようとしているわけではない。提供する機能では重なる部分もあるが、同じ事をしようとしているわけではないのだ。
jQuery自身による説明では、HTML、イベント、アニメーション、Ajax、そしてWeb開発について述べられている。MooToolsはオブジェクト指向とパワフルで柔軟なコードについて説明している。jQueryは「JavaScriptの書き方を変える」ことを目指し、一方でMooToolsは上級のJavaScript開発者にとっての媒介となることを目指している。
この問題はある意味、フレームワークVSツールキットという考え方である。MooToolsはJavaScriptをあるべき姿(MooToolsの作者にとって)へと拡張するフレームワークである。目的はJavaScriptのようなAPIを実装し、すべてを高機能にすることである。DOMだけではない。jQueryはDOMそれ自体がもっと面白くなるように設計された独自システムによって、簡単に使える一連のメソッドを提供するツールキットである。JavaScriptを書くときに多くの人が注力する部分がDOMであるため、多くの場合において、jQueryが要求を満たす。
MooToolsを使っているとき、あなたの書いたコードはJavaScriptのように見えるだろう。言語としてのJavaScriptに興味が無ければ、MooToolsを学ぶのは退屈な仕事だ。もしJavaScriptに興味があり、どうすれば面白くなるのか、パワフルになるのか、印象的になるのか、という考えているならば、私が思うに、MooToolsはよりよい選択肢である。
学習曲線とコミュニティ
まず、jQueryは概して学ぶのが簡単である。ほとんど口語体のスタイルを持っていて、プログラミングのようには見えない。JavaScriptを学習することなしにとっととなにかを動かしたいならば、jQueryは恐らくよい選択だ。MooToolsが同じ事を成し遂げるために役立たないというのではないが、MooToolsがJavaScript初心者にとってコツを掴むのが少し難しいということと、jQueryに関する情報がそこら中にあるということ(少なくともMooToolsに関する情報よりは)という点は認めねばならない。
jQueryコミュニティ(jQueryの “Discussion” ページ) とMooToolsコミュニティ (irc、メーリングリストそして 非公式フォーラム)を比べれば、すぐ二つのことに気付くだろう: 1) jQueryコミュニティの方がはるかに大きい(私はこれが上述した簡単に学ぶことができるという点にもっとも寄与していると思うが、同時に…)。 2) ライブラリの宣伝に熱心である。jQueryとMooToolsを使用人数、Googleの検索数、関連書籍数などの指標で比較すれば、jQueryが大はばにリードしていることがわかるだろう。
MooToolsのことを考慮に入れるべき理由を挙げる前に、私は二つのフレームワークが何を行っているのかについて少し語らねばならないだろう。最終的にどのフレームワークを選ぶべきかという問題は、何を成し遂げ、どのようにプログラミングをしたいのか(少なくともJavaScriptにおいてプログラミングをしたければ)という問題に帰着する。
JavaScriptは何に向いているか
この選択はある意味であなたがJavaScriptでしたいことを教えてくれる。ヴァニラJavaScript——フレームワークなしの、古いプレーンなJavaScript——のことを考えてみよう。JavaScriptはあなたにString、Number、Function、Array、Date, 正規表現などの組み込みオブジェクトを提供してくれる。そしてまた、継承のモデル——プロトタイプ型継承(これについては後述する)と呼ばれるいささかな難解なモデル——も提供してくれる。これらの組み込みオブジェクトと継承のコンセプトはすべてのプログラミング言語におけるパンとバターのようなものであり、ブラウザやweb、CSSやHTMLとはまったく関係がない。JavaScriptで何でも書きたいことができるのだ。○×ゲーム、チェス、写真編集、webサーバー、なんでもだ。世にある99%のJavaScriptはブラウザ上で実行されており、ブラウザのためのプログラミング言語だと思ってしまうのはよくあることだが。
ブラウザ、DOMを理解することが、JavaScriptに対して費やす時間のほとんどを占めている。しかし、JavaScriptが実際には堅牢で印象的なプログラミング言語であることを知れば、MooToolsとjQueryの間の違いを理解する手助けになるだろう。
DOMだけよりももっと
JavaScriptで成し遂げたいことを「ページに何かを仕込んでおいて、動作させる〔訳注:”get stuff on the page and do stuff to it”〕」という観点だけから見るのならば、jQueryがおそらく最良の選択だろう。プログラミングに見えないときさえあるようなやり方だが、ページでの動作をそのまま説明するような印象的なシステムを提供している点においてjQueryは抜きん出ている。JavaScriptの残りの部分を使ってやりたいことを実現できるが、もしもDOMに焦点をあてる——CSSプロパティを変更する、アニメーションさせる、AJAXでコンテンツを取得する——ならば、記述したものの大半はjQueryへと変換され、古めかしいJavaScriptのようには見えないだろう。jQueryはDOMとは関係のないメソッドをいくつか提供している。たとえば、配列への反復処理メカニズム——$.each(array, fn)——であり、文字列のトリムメソッド——$.trim(str)——である。この類いのユーティリティメソッドは多くあるわけではないが、それでいいのだ。なぜなら、あなたがDOMで何かをしようとしているほとんどの場合、DOMに反復処理を行ったり、何らかの方法(htmlの追加、スタイルの変更、クリックやマウスオーバーへのイベントリスナー添付)で変換したりするだけである。それほど多くは必要ないのだ。
しかし、もしもJavaScriptの対象を最大限に広げようと思っているならば、jQueryがDOM以外に焦点を当てていないことに気づくだろう。これはjQueryを簡単に学べる理由の一つであるが、同時にjQueryがJavaScriptを書く助けとなる限界でもある。DOMのためのプログラミングシステム以外のものになろうとはしていないのだ。継承もサポートしないし、JavaScript言語に組み込みの型に基本的なユーティリティを提供することも目指していない。その必要はないのだ。文字列、日付、正規表現、配列、関数などで何かをしたいならば、することはできる。それを手助けするのはjQueryの仕事ではない。言語としてのJavaScriptはあなた次第である。jQueryはDOMをあなたにとっての楽しい遊び場にしてくれるが、JavaScriptの残りの部分に焦点は合っていない。
ここがMooToolsと大きく異なる点である。DOMに焦点を合わせる代わり(後述するが、jQueryが実現できることを全く違った方法で提供する)に、MooToolsは言語全体に焦点を合わせている。jQueryがDOMを遊び場にしてくれるのならば、MooToolsの狙いはJavaScriptを遊び場にすることであり、それこそが学ぶのに難しい一因となっている。
JavaScriptでの継承
プログラミング言語としてのJavaScriptには驚くべき一面がある。はじめは関数を他のオブジェクトと同じように変数に入れて引き回せる高階オブジェクトとして扱うに関数型言語に見える。こうしたコンセプトの元に設計されており、多くのメソッドや制御構文はそれに従ったコードを書くことでうまく働くのだ。つまり、下記のコードには違いがある:
for (var i = 0; i < myArray.length; i++) { /* do stuff */ }
myArray.forEach(function(item, index) { /* do stuff */ });
JavaScriptはそれほど独特ではない継承モデルを持っているが、少なくとも他のプログラミング言語には見られないものである。サブクラスを持てるよう定義されたクラスの代わりに、プロトタイプ継承に従ったオブジェクトを渡すのだ。これはつまり、オブジェクトが他のオブジェクトから直接継承するということである。他のオブジェクトを継承するオブジェクトのプロパティを参照するとき、JavaScriptは子オブジェクトのプロパティを見て、見つからなければ親オブジェクトのプロパティを探しにいく。これがたとえば配列のメソッドが動く仕組みである。このようにタイプしたとしよう:
[1,2,3].forEach(function(item) { alert(item) }); //まず1をアラート、そして2、3
forEachメソッドはあなたが宣言した配列 ([1,2,3])のプロパティではなく、すべての配列のプロトタイプのプロパティである。このメソッドを参照すると、JavaScriptはあなたの宣言した配列のforEachというメソッドを探し、それがなければ、すべての配列のプロトタイプを調査する。つまり、forEachメソッドはすべての配列の各メモリにはなく、配列のプロトタイプのメモリ内にのみ存在する。これは信じられないほど効果的で、とてつもなくパワフルである。(余談:MooToolsはforEachメソッドのエイリアスとしてeachを使う)
自己参照
Javascriptには特別な言葉”this”がある。”this”がいったいなんであるかを簡潔に定義するのは難しいが、デフォルトだと”this”は現在のメソッドが属するオブジェクトを指す。これにより、オブジェクトは自分のメソッドの中で自分を参照することができる。これは子オブジェクトを作り、それが膨大な数のメソッドを持つような場合、重要になる。他にどうやってメソッドがオブジェクト自身を参照するというのだろう。メソッドのコピー実体が親オブジェクトの中に存在し、子オブジェクトの中でない場合、この”this”キーワードはこれらのインスタンスが自身の状態を参照できるようにしてくれる。(“this”キーワードに関する詳細と Mozillaによるもの)
“this”キーワードは他のオブジェクトを継承したオブジェクトが自身を参照することを可能にするが、”this”を通して何か他のものを参照したいときもあるだろう。これはバインドと呼ばれ、メソッドに異なった“this”を結びつける手法である。配列の”each”メソッドは第二引数に特定のオブジェクトを渡すことで、バインドさせることができる。以下が異なる”this”を渡す例である:
var ninja = { weapons: ['刀', '手裏剣', '火遁の術'], log: function(message) { console.log(message); }, logInventory: function() { this.weapons.each(function(weapon) { //"this"にはninjaを指してほしい... this.log('このninjaは' + weapon + 'で殺すことができる'); }, this); //これで"this" (ninja)をArray.eachに渡せる } }; ninja.logInventory(); //このninjaは刀で殺すことができる //このninjaは手裏剣で殺すことができる //このninjaは火遁の術で殺すことができる
上記の例では、ninjaオブジェクト(logInventoryメソッドの中で”this”となる)を配列のメソッドにバインドしているので、ninjaのlogプロパティから参照することができる。これをしなければ、”this”はwindowになる。
これらはJavaScriptが提供すべきパワー——継承、自己参照、バインド、プロトタイプ・プロパティ——の例にすぎない。悪い知らせは、ヴァニラJavaScriptがこれらのパワフルな力を使いやすく提供しておらず、MooToolsがはじめたということだ。そのおかげで、これらのパターンは簡単で楽しいものになる。より抽象的なコードを書くことになるが、長い目で見れば、素晴らしくパワフルなことだ。これらのパターンにどれほどの価値があり、どのように使うのかを学ぶのは努力を要するが、その一方であなたの書いたコードは再利用可能でメンテナンスが簡単になる。この二つの事柄について、もうちょっと語らせてもらおう。
MooToolsはJavaScriptそのものをもっと楽しくする
MooToolsはJavaScriptのAPIをそのものをもっと安定してわかりやすいものにすることを目指しているので、”JavaScriptの書き方を変える”ことや、JavaScriptをまったくストレスのないものにすることにはさほど注力していない。MooToolsはJavaScript言語を拡張しようとしている。MooToolsはJavaScriptをあるべき姿にしようとしているのだ。coreライブラリのために費やされた労力のほとんどは、Function、String、Array、Number、Elementその他のプロトタイプを改善するためだった。そして、その他に提供する重要な機能がClassと呼ばれる関数である。
さて、Classは多くの人にとってJavaやPHPで見られるようなクラス型の継承モデルを模倣しようとしているように見えるだろう。しかし、この場合は当てはまらない。Classが行っているのは、JavaScriptのプロトタイプ型継承モデルにアクセスしてその利点を活用することを簡単にすることだ。注意していただきたいのは、このコンセプトがMooToolsに特有のものではなく(他のフレームワークも似たような機能を提供している)、また、これらのコンセプトがjQueryには見られないということである。jQueryは継承システムを提供していないし、組み込みオブジェクト(Function、Stringなど)の拡張も行っていない。これはjQueryの落ち度ではない。jQueryの開発者はこれらの機能を簡単に提供できるからだ。彼らはむしろ、違う目的でツールキットとして設計したのだ。MooToolsがJavaScriptを楽しくすることを目指す一方、jQueryはDOMを楽しくすることを目指し、その設計者はなすべきことの範囲を制限することを選んだのだ。
jQueryはDOMをもっと楽しくする
以上が、jQueryがわかりやすい理由である。JavaScriptの内と外を詳しく学ぶ必要はない。あなたはプロトタイプ型継承や、バインド、”this”、そして組み込みプロトタイプの深淵に投げ込まれることはない。公式チュートリアルにしたがってjQueryを始めれば、以下のようなコードにでくわすだろう:
window.onload = function() { alert("ようこそ"); }
これが三番目である:
$(document).ready(function() { $("a").click(function(event) { alert("来てくれてありがとう"); }); });
MooToolsの本かMooToolsのチュートリアルを読めば(両方とも私が書いた)、もっと違ったところから始めているのがわかるだろう。最初を飛ばして、さっさとエフェクトとDOMから学ぶこともできるが、MooToolsを学びたければ、Classなどから始める方がいいだろう。そして、もしプログラミングが始めてだったり、JavaScriptのすべてを学ぶ前にサイト上の何かを動かしたかったら、jQueryの方がもっと親切である。
一方、JavaScriptそのものを学びたかったら、MooToolsは素晴らしい選択だ。MooToolsはJavaScriptが持つはずの機能(組み込みオブジェクトを拡張するメソッドのほとんどはJavaScript 1.8の仕様に則ってさらにそれを発展させてたものである)を実装している。もしプログラミングに親しんでおり、とりわけオブジェクト指向と関数型プログラミングに慣れていれば、エキサイティングで印象的なデザインパターンを備えているMooToolsはうってつけだろう。
あなたのできることを私はもっとよくできる
jQueryができることで、MooToolsにもできることがたくさんある。MooToolsにできることで、jQueryには移植する方法がないものがある。なぜなら、jQueryはDOMに焦点を当てているからだ。MooToolsはjQueryよりも大きな機能を備えているが、それを実現できないかどうかは、jQueryとは関係ない。たとえば、jQueryは継承のシステムを持っていないが、それでいいのだ。もし実現したいなら、MooToolsのClassをjQueryと一緒に使って実現することができる(もしくは、自分で書くか)。jQueryのための継承プラグインだってある(私は使っていないが、ほとんど似たような機能を提供するだろう)。
上記のことを、jQueryの例で見てみよう:
$(document).ready(function() { $("a").click(function(event) { alert("来てくれてありがとう!"); }); });
これをMooToolsに翻訳すると、こうなる:
window.addEvent('domready', function() { $$('a').addEvent('click', function(event) { alert('来てくれてありがとう!'); }); });
とてもよく似ていないだろうか?
jQueryのもっと複雑な例を見てみよう:
$(document).ready(function() { $("#orderedlist li:last").hover(function() { $(this).addClass("green"); }, function() { $(this).removeClass("green"); }); });
そしてMooTools:
window.addEvent('domready',function() { $$('#orderedlist li:last').addEvents({ mouseenter: function() { this.addClass('green'); }, mouseleave: function() { this.removeClass('green'); } }); });
ほら、とても似ている。私はMooTools版がより明白で、口語的だと思う。二つのイベント――一つはマウスエンターでもう一つはマウスリーブ――を添附したコードではMooToolsの方が読みやすい。jQuery版の方はより簡潔である。jQueryのhoverメソッドは二つのメソッドを受け入れる――一つ目がマウスエンターで、二つ目がマウスリーブ。私は個人的に、MooToolsの読みやすいコードが好きだが、これはあくまで主観である。
ときにjQueryは私の趣味に合わず難解である。メソッドは見ただけでは理解できず、解釈するのが難しい。私はMooToolsに慣れ親しんでいるのでおり、MooToolsを読みやすいと思っているので、やや不公平かもしれない。しかし、MooToolsに認める美徳は、ほとんどすべてのものを正しく名付けているということである。メソッドはすべて動詞であり、何をするかについての疑いはほとんどない。すべてのプログラミング言語は書くときにリファレンスを訪れ、構文を参照しなくてはならない――などと言うつもりはない。私が言いたいのは、MooToolsのAPIがとても理解しやすいということである。
MooToolsはあなたにあなたの道を歩ませる
みなさんはjQueryの構文のどこが水なのだろうか? MooToolsのパワーを示すよい例は、あなたの流儀にMooToolsを従わせることがいかに簡単かということだ。もしjQueryのhoverメソッドをMooToolsで実装したかったら、簡単にできる:
Element.implement{{ hover : function(enter,leave){ this.addEvents({ mouseenter : enter, mouseleave : leave }); } }); //これでjQuery版と同じよう使うことができる: $$('#orderlist li:last').hover(function(){ this.addClass('green'); }, function(){ this.removeClass('green'); });
実際、MooToolsにはこれを実現するプラグインがある。MooToolsにjQuery構文を与えるのだ。MooToolsが拡張性に焦点を与えているということは、やりたいことをなんでもできるということである。これはjQueryができないことの一つである。MooToolsはjQueryを真似することができるが、jQueryはMooToolsを真似ることはできない。クラスを書いたり、組み込みのプロトタイプを拡張するようなことがしたければ、MooToolsがそれを可能にしてくれる。自分で書いてもいいのだが。
デザインパターンとしての連鎖
他の例も見てみよう。こちらはjQueryの例だ(jQueryのチュートリアルより):
$(document).ready(function() { $('#faq').find('dd').hide().end().find('dt').click(function() { $(this).next().slideToggle(); }); });
これは私が個人的に好きではない例の一つだ。上のコードを見ても、何をしているのかがわからない。もっと言えば、.endとそれにつづく.findが.endのすることにどのように関わっているかが興味深くさえある。そこで、jQueryのリファレンスを参照すると、.endのしていることがわかる(もともとのセレクタの値、この場合は#faqを返す)。しかし、これは非常に奇妙に思える。jQueryを使うとき、なんのメソッドが返ってくるのかがわからないのだ。jQueryを多くの人は喜んで使っているのだから、私以外の人がこれに困らされているということはない。そこで、再び私の個人的な好みにを挙げてみよう。
上のロジックをMooToolsで見てみよう:
window.addEvent('domready', function() { var faq = $('faq'); faq.getElements('dd').hide(); faq.getElements('dt').addEvent('click', function() { this.getNext().slide('toggle'); }); });
またもやMooToolsは少し冗長のようだが、明白である。また、このデザインパターンでもjQueryが.endメソッドで返した#faqへの変数による参照は残っている。MooToolsでも高度に連鎖されたコードを書くことは可能だと付け加えておこう。次の例である:
item.getElements('input[type=checkbox]') .filter(function(box) { return box.checked != checked; }) .set('checked', checked) .getParent()[(checked) ? 'addClass' : 'removeClass']('checked') .fireEvent((checked) ? 'check' : 'uncheck');
しかし、実際のところ、このようなコード――domreadyステイトメントに多くのロジックが詰まっている――を書くのは、他のフレームワークにおいても、よくない方法だ。ロジックは再利用できる固まりとしてカプセル化した方がはるかにいい。
jQueryでのコード再利用
Webプロジェクトに携わっているとき、このようなコードを書いてしまいがちだ。DOM要素を選択し、隠したり、他の要素を変更させたり、マウスオーバーやクリック用のイベントリスナーを”設定”させる、というだけのコードである。このようなやり方でコードを書くのは効果的で、早い。Domreadyステイトメントにすべてのロジックを書くこのやり方に潜む問題は、同じ事を行うコードが色んな場所に生まれてしまうということである。もしFAQパターンを取れば、用語とその定義からなるリストがどこにあろうと、たとえ違うページにあっても、同じロジックを適用することができる。このパターンを適用するとき、毎回同じロジックを繰り返すべきだろうか?
ロジックを再利用可能にする簡単な方法は、それをfunctionで囲み、引数を渡すことである。jQueryではこのようになる:
function faq(container, terms, definitions) { $(container).find(terms).hide().end().find(definitions).click(function() { $(this).next().slideToggle(); }); }; $(document).ready(function() { faq('#faq', 'dd', 'dt'); });
こちらの方が優れているのには、二つの重大な理由がある:
- もしも明日、これらのリストの挙動を変更(たとえば、クリック追跡ロジックを導入してwebログからクリックされたものを推測したり、定義をAjaxによって取得したり)することになったら、メインとなるfaqメソッドを変更するだけで、それを使うすべての場所が変更される。もしくは、新しいバージョンのjQueryがリリースされ、挙動が変わっていたら、そこら中にあるメソッドをすべて直す代わりに、一つのメソッドを更新するだけでよい。私はこれを「アプリケーションのフットプリントを小さく保つ」と言う。アプリケーションが私の書いた汎用的なコードに接する部分が少ないほど、バグフィックス、フレームワークの更新、機能追加、機能変更などが簡単になる。
- 二つ目の理由は、コードが少ないということである。同じメソッドを何度も再利用することで、私自身は同じ事を繰り返さずに済む。これはすべてのプログラミング環境において価値のあることだ。そしてまた、ユーザがダウンロードしなければならないコードの量も減らしてくれる。
jQueryは実際のところ、こうした再利用可能な”ウィジェット”を書くための洗練されたシステムを徐々に持つようになっている。上で挙げたようにfunctionの中にコードを入れるのよりも、むしろjQueryプラグインを書くよう勧めている。以下のようなものである:
jQuery.fn.faq = function(options) { var settings = jQuery.extend({ terms: 'dt', definitions: 'dd' }, options); //"this"は現在のコンテキストである。この場合では、faqレイアウトに変換したい要素となる $(this).find(settings.terms).hide().end().find(settings.definitions).click(function() { $(this).next().slideToggle(); }); return this; };
これで、次のように使うことができる:
$('#faq').faq();
上の例を見ると、このやり方でfaq関数を宣言することと、単独の関数として宣言することには違いがないように見える。幸いなことにグローバル名前空間にはないが、独自の名前空間に追加することは簡単にできる。jQueryに追加することで、他のjqueryメソッドと連鎖させることができるのだ。その他のメリットとして、関数内の”this”はjQuery連鎖のどこにいようと、現在のコンテキストを指すということである。このパターンをプラグインに利用することで、プラグインがjQueryの一部であるかのように見せることができるが、その一方、プラグインは基本的に現在のjQueryコンテキストを持ち、コンテキストに対して働きかけ、連鎖上の次のアイテムのためにコンテキストを返す、単一の関数である。複雑なことは特にないため、誰でもjQueryプラグイン――ただの関数――を書くことができる。
jQueryを使って、メソッドと変数をたくさん持った複雑なプラグインを書くこともできる。この類のパターンはjQueryのUIプラグインシステムでサポートされており、基礎的なプラグイン(例に挙げたfaq)と同じ仕組みを用いない。その代わりに、メソッドとプロパティを持ったオブジェクトをjQueryオブジェクトに添附する(例. $.ui.tabs)。このオブジェクトを呼び出すショートカットがある($(selector).tabs())ので、faqプラグインと同じように連鎖させることができる。しかし、セレクタにひっかかったアイテムのために作られたタブオブジェクトへの参照を返さないので、もう一度同じセレクタを使ってそのメソッドを呼び出さなくてはならない。myTabInstance.add(url, label, index)を呼び出す代わりに、もう一度同じセレクタを実行し、関数をその名前(文字列として)で呼び出さなくてはならないのだ: $(selector).tabs(‘add’, url, label, index); これはセレクタを二度起動していることになる(どこかの変数に格納していないかぎり)ので、もはや”add”メソッドに対してのポインタはなく、バインドやディレイ(遅延)を行うことができない。この問題はMooToolsとjQueryのcoreに焦点を絞ったものである。jQueryのUIシステムはこの機能を提供するが、デフォルトで備わっているものではない。
MooToolsでのコード再利用
MooToolsでパターンを定義するなら、Classか組み込みオブジェクト(たとえばString)へのimplementメソッドを使うのがよいだろう。
JavaScript本来のスタイルと完全に異なる言語を提供するのではなく、MooToolsは自前の構文を定義することとJavaScript独自のデザインパターンを拡張することを選んだ。その一つが、言語とDOMに組み込まれているオブジェクトのプロトタイプを拡張することである。つまり、文字列をトリムするメソッドが必要なら、MooToolsはString自体にtrimメソッド(String.trimはMooToolsにすでに組み込まれているので、あなたが自分で加える必要はない)を付け加えることであなたを励ましてくれる:
String.implement({ trim: function() { return this.replace(/^\s+|\s+$/g, ''); } });
これにより、“終わりにスペースはないよ! “.trim()を実行すれば、“終わりにスペースはないよ!”が返ってくる。ある人はもとからあるプロトタイプにプロパティを実装することは不適切だというかもしれない。これはMooToolsとPrototype.jsが一緒に使えない理由でもある。備え付けのプロトタイプを拡張するフレームワークは同時に動かない。もし同じページにおいてString.prototype.foo() を定義し、後から再度同じものを定義したら、最後に来た者が常に勝つ。ある意味、これはグローバルwindow名前空間で直面する問題と似ている。〔しかし、〕これはJavaScriptの仕様である。これは JavaScript 1.8が沢山の機能を追加したのと同じやり方である。プロトタイプに様々なものを追加しているのだ。
MooTools開発者は素晴らしいフレームワークを用意し、あなたが独自の機能を拡張しやすくしている。採用したフレームワークだけが使われるのだ。二つのフレームワークをユーザーにダウンロードしてもらうようお願いするのは、野暮なことである。双方のフレームワークをインクルードする場合、その理由はただ一つ、あなたが両方のプラグインを使いたいからである。MooToolsの開発者(私も含む)としては、選択したフレームワーク内に使わない機能があれば、ユーザーにそれをダウンロードしてもらうより、自分の環境に移植する方が適切だと思う。
一度JavaScriptの動き方を知り、組み込みオブジェクトを拡張する力を知ったならば、まったく新しいプログラミングの世界が開ける。ElementやDate、Functionを変更するプラグインが書けるのだ。組み込みのオブジェクトにメソッドを追加するやり方は汚染だという人はいるだろうが、これはJavaScriptが本来想定されている使われ方である。言語の機能としてそう設計されているのだ。組み込みのオブジェクトにメソッドを追加することで、コードを簡潔ではっきり分けられたものにすることができる。jQueryもこれを行っているが、プロトタイプの拡張はjQueryオブジェクトに限られている。
jQueryオブジェクトに対して複数のメソッドを連鎖させることは簡単だが、他のタイプのオブジェクトに対しても同じことをしなければならない。たとえばjQueryにおいて、文字列をトリムして各行に反復処理を行いたい場合、このように書かねばならない:
$.each( $.trim( $('span.something').html() ).split("\n"), function(i, line){ alert(line); });
しかし、MooToolsはプロトタイプを拡張しているので、このようにできる:
$('span.something').get('html').trim().split("\n").each(function(line){ alert(line); });
この例を見れば、プロトタイプの修正がいかに強力かがわかるだろう。連鎖が便利なのはDOM要素だけではない。MooToolsはどのオブジェクトに対しても連鎖を可能にし、複数の要素に対して一度にメソッドを実行することもできる。
ここで鍵となるのは、MooToolsフレームワークの心髄は、あなたが何かを達成するためにプログラミングをしているという考えにある。coreに含まれない機能があれば、あなたはそれを拡張し、付け加えることができる。coreに求められるのは、みんなが欲しかった細々とした機能を提供することではなく、あなたが実現したいことを可能にしてくれるツールである。そのほとんどはもとからあるプロトタイプの拡張を簡単にすることと、プロトタイプ型継承の利点を活用することである。これはヴァニラJavaScriptでも可能だが、MooToolsはそれをもっと簡単で楽しいことにしてくれる。
MooToolsと継承
その名前にも関わらず、MooToolsのClass名数は実際にクラスではなく、クラスを生成しない。もっと伝統のあるプログラミング言語のクラスを思い起こさせるが、実のところClassはオブジェクトとプロトタイプ継承そのものなのである。
クラスを作るには、Classコンストラクタにオブジェクトを渡す:
var Human = new Class({ initialize: function(name, age) { this.name = name; this.age = age; }, isAlive: true, energy: 1, eat: function() { this.energy = this.energy + 1; //this.energy++ に同じ } });
Classにオブジェクトを渡した(上の例では、”isAlive”や”eat”などのメンバーを持ったオブジェクト)ので、このオブジェクトがこのクラスのすべてのインスタンスのプロトタイプとなる。インスタンスを作るには、このように呼び出せばよい:
var bob = new Human("bob", 20); //ボブの名前は"bob"で、20歳である
これでHumanのインスタンスができた。bobはHumanクラスを作ったときに定義したオブジェクトのプロパティを持っている。しかし、重要なのはbobが継承によってこれらのプロパティを持っているということである。bob.eatを参照するとき、bob自体はこのプロパティを持っていない。JavaScriptはbobを調査し、eatメソッドがないことを確認すると、継承の連鎖を辿ってHumanクラスを作る時に渡したオブジェクトを発見する。これはenergyに関しても同様である。一見すると、これは潜在的によくないことに思われるかもしれない。bobがeatしたからといって、すべてのhumanインスタンスにenegryを増加されたくはない。理解すべき重要なtねは、一度bobのenergyに値を登録すると、彼独自の値を登録したことになり、プロトタイプを探しにいかなくなるのだ。したがって、一度bobがeatすれば、彼独自のenergy(2)が定義される。
bob.eat(); //bob.energy == 2
bobのnameとageは彼独自のものであることに注意してもらいたい。これらの値はinitializeメソッドで初期化されたときに登録されている。
こうした事は少し奇妙に思われるかもしれないが、重要なのはパターンに則った機能を定義でき、必要な時にそのインスタンスを生成できるということだ。それぞれのインスタンスは独自の変数を保持している。したがって、別のインスタンスを作ればそれは他のものと独自に存在するが、同じパターンから継承されているのだ:
var Alice = new Human(); //alice.energy == 1 //bob.energy == 2
この挙動について理解したいと思うようになると、もっと面白くなる。
クラスの拡張と実装
jQueryのfaqプラグインをもう一度見てみよう。このプラグインにもっと多くの機能を追加しようとすると、なにが起きるだろうか。サーバから回答を取得するajaxバージョンを作りたいとしたら? faqプラグインが他の人に作られており、それ自体を全くいじることなく機能を追加したい(くまなく見たくはない)のだ。
現実的な選択肢は、faqプラグインのロジックを完全に複製(単一の関数だということをお忘れなく)するか、関数の内部をくまなく見るか、呼び出してから新たにロジックを付け加えるかだ。与えられた選択肢では、最後のものが一番トラブルが少なそうだ。以下のようになる:
jQuery.fn.ajaxFaq = function(options) { var settings = jQuery.extend({ //termを要求するurlなどのajax用オプションを指定する url: ...definitions: 'dd' }, options); //"this"は現在のコンテキスト。この場合はfaqレイアウトを適用したい要素 $(this).find(settings.definitions).click(function() { $(this).load(.....); //用語からその説明を読み込む }); this.faq(); //オリジナルのfaqプラグインを呼び出す });
これにはいくつか欠点がある。まず、faqクラスは拡張されているかもしれない定義内容を取得するために、セレクタを繰り返すことになる。取得した定義内容を保存しておく手段も、二度目に必要になる定義を保存しておく手段もない。次に、定義を表示するためのAjaxロジックをfaqプラグインが定義内容を表示するロジック自体に追加することができない。もともとのプラグインはslideToggleと呼ばれ、エフェクトを用いて定義内容を展開するものである。このエフェクトはAjaxがロードを終える前に開始される可能性があるので、やや問題がある。faqプラグイン全体を複製せずにこの問題を解決することできない。
ここでMooToolsのHumanクラスを思いだそう。これはisAliveやenergyのようなプロパティーとeatというメソッドを持っていた。新しいバージョンのHumanにプロパティを追加するにはなにをしたらよいだろうか? MooToolsではクラスを拡張する:
var Ninja = new Class({ Extends: Human, initialize: function(name, age, side) { this.side = side; this.parent(name, age); }, energy: 100, attack: function(target) { this.energy = this.energy - 5; target.isAlive = false; } });
サブクラスにたくさんの機能を追加したのがわかるだろう。このサブクラスはHumanの持っていたすべてのプロパティを継承している。Ninjaはenergyの初期値として100を持っている。Ninjaはsideを持つ。また、attackメソッドを持ち、他のHumanを殺すことができるが、Ninjaはenergyを5消費する。
var bob = new Human('Bob', 25); var blackNinja = new Ninja('ニン テンドー', 'unknown', 'evil'); //blackNinja.isAlive = true //blackNinja.name = 'ニン テンドー' blackNinja.attack(bob); //bobに生き残るチャンスはない
これをちょっと見てみるだけで、考察に値する興味深いことが幾つかわかる。Ninjaクラスにinitializeメソッドがあることに注意して欲しい。これはHumanクラスのinitializeメソッドを上書きしているように見えるが、this.parentを呼び出して親クラスが想定していた引数を渡せば、まだ利用可能であることがわかる。さらに、親クラスを呼び出す前、または後など、自分が付け加えたロジックがいつ発生するのかをコントロールできる。新しい値をプロパティ(energy値のような)に設定したり、新しい機能を登録できる。これがjQueryのfaqプラグインでできたら、と想像してほしい。Ajaxを読み込み、それから値をスライドして表示できるようになるのだ。
MooToolsはMixinと呼ばれる別のパターンを持っている。 あるクラスからサブクラスへと拡張するような親子関係ではなく、異なるクラス同士のプロパティを混ぜ合わせたクラスを定義することができる。以下がその例である:
var Warrior = new Class({ energy: 100, kills: 0, attack: function(target) { target.isAlive = false; this.energy = this.energy - 5; this.kills++; } });
これにより、NinjaがHumanと異なっていた性質はなくなり、一つのクラスに統合できる。これでNinja以外の部分でこのコードを再利用できる。Warriorの性質を持つクラスを作ってみよう:
var Ninja = new Class({ Extends: Human, Implements: Warrior, //配列にして、複数を指定することも可能 initialize: function(name, age, side) { this.side = side; this.parent(name, age); } });
Ninja は以前のように動いているが、Warriorはまだ再利用可能である:
var Samurai = new Class({ Extends: Human, Implements: Warrior, side: 'good' });
これでSamuraiとNinjaができた。とを定義するためのコードがどれだけ短いかわかるだろうか? この二つは戦士の資質をもつ人間として似ているが、侍が常に善である一方、忍者は忠誠心が傾きやすい。HumanクラスとWarriorクラスを書く時間をかければ、コードを反復することなく三つのクラスを書くことができ、それらがお互いにどう影響していおり、メソッドが呼び出されるときのコントロールがどのような粒度であるかがわかるので、メンテナンスが用意である。それぞれのインスタンスは独立して存在し、コードは読みやすい。
さて、MooToolsでクラスがどのような働きをするかをざっと見たところで、jQueryで書いたfaqクラスに立ち戻り、Ajax機能を追加しながらMooToolsで書いてみよう。
var FAQ = new Class({ //OptionsはMooToolsで提供される他のクラスである Implements: Options, //これはデフォルトのオプション options: { terms: 'dt', definitions: 'dd' }, initialize: function(container, options) { //コンテナへの参照を保持 this.container = $(container); //setOptionsはOptionsのmixinによって提供される //メソッドで、渡された値を初期値に設定する this.setOptions(options); //termsとdefinitionsを保持 this.terms = this.container.getElements(this.options.terms); this.definitions = this.container.getElements(this.options.definitions); //独自メソッドとしてattachを呼び出す //クラスの拡張が容易になる this.attach(); }, attach: function(){ //termsをループ処理 this.terms.each(function(term, index) { //それぞれにクリックイベントを添附 term.addEvent('click', function(){ //toggleメソッドを現在のindexで呼び出す this.toggle(index); }, this); }, this); }, toggle: function(index){ //与えられたindexのdefinitionをトグルして開く this.definitions[index].slide('toggle'); } });
ワオ。なんて多いコードだ。全部のコメントを削除しても、まだ20行以上ある。すでに上で示したように、jQuery版のコードと同じだけ書いたというのに、骨組みができただけだ。なぜこんなに長くなるのか? そう、我々はこれをもっと柔軟な形にする必要がある。クラスを使い、このようにコンストラクタを呼び出すのだ:
var myFAQ = new FAQ(myContainer); //これでメソッドを呼び出すことができる myFAQ.toggle(2); //三番目の要素をトグル
インスタンスのプロパティとメソッドにアクセスできる。しかし、Ajax機能についてはどうしのか? jQuery版のAjax拡張の問題点は、definitionの展開を読み込み完了まで待たせられないことだった。MooTools版ではこの心配がない:
var FAQ.Ajax({ //このクラスはFAQのプロパティを継承している Extends: FAQ, //その他の初期値に加え、いつくかのオプションを新たに追加 //一つはtermを送るためのurlがそれである。ここにtermのindexを送る //このコードはもっとよくできるが、とりあえずはこれで目的を果たす options: { url: null; }, //結果をキャッシュし、セクションが二回ヒットされたら、 //サーバにデータは取りにいかない indexesLoaded: [], toggle: function(index){ //definitionをすでに読み込んでいたら if (this.indexesLoaded[index]) { //親クラスのtoggleを呼び出すだけ this.parent(index); } else { //そうでなかったらサーバへリクエスト new Request.HTML({ update: this.definitions[index], url: this.options.url + index, //データが読み込まれたら、definitionを展開する onComplete: function(){ this.indexesLoaded[index] = true; this.definitions[index].slide('toggle'); } }).send(); } } });
これでサーバからdefinitionを取得するFAQクラスの新しいバージョンができた。サーバから値が戻ってくるまでdefinitionを展開しないロジックへと拡張することもできる(jQuery版ではできなかったことだ)。また、新しい機能(Ajax)やその他については、ほとんど記述する必要がなかったことも留意してほしい。この拡張性により、様々な機能を持つプラグイン・ファミリーを作ることができる。つまり、他の人が作ったプラグインを利用して、それをほんの少しカスタマイズし(ソースコードをくまなく見ることなく)、好きなように使うことができるのだ。これは与えられたデザインパターン――デイト・ピッカー、タブ・インターフェースなど――のプラグインがMooToolsには少ししかない理由である。ほとんどのプラグインで問題は解決するか、そうでなければそれをニーズに合わせて拡張すればいいのだ。
最初に指摘したとおり、メソッドと変数を持つ複雑なjQueryウィジェットを書くことは可能である。これでDOMと関係のないロジックに力点を置いたりすると、あなたの書くコードの大半はヴァニラJavaScriptになる。jQueryのモデルはインスタンスをサブクラスへと拡張するシステムを提供しない。簡単な再利用を可能にするmixinも提供してくれない。結果的に、jQueryのプラグインは常にDOM要素と密接に関わっているのだ。もしURLを解析するようなクラスを書きたければ、有用なシステムは存在しない。自分でそれを書かない限りは。
決断の時
jQueryが印象的であること、素早く簡単にコーディングできること、そして、DOMに焦点を当てる一方、MooToolsは拡張性、継承、読みやすさ、再利用、メンテナンスしやすさなどに重きを置いている。この二つを両極端にすえるならば、jQueryははじめるのも作るのも簡単だが(私の経験では)メンテナンス性と再利用が難しく(しかしそれはあなた次第であり、同様にjQueryの問題でもない)、MooToolsは学ぶのにも完成までに要するコードを書くのにも時間がかかかるが、できあがってしまえば再利用もメンテナンスもしやすい、ということになる。
さら、MooTools CoreはjQueryができるとはとても思えないたくさんの機能を持っている。双方のフレームワークとも、Coreに無駄はなく、プラグインは拡張を書けるようにしてある。Coreの仕事は欲しい機能を提供することではなく、思いつくものを実装するためのツールを提供することである。これがJavaScriptとそのフレームワークのパワーであり、これら二つのフレームワークはその点において優れている。MooToolsはより総合的なアプローチをとり、思いつくことすべてをDOMの範囲を超えて提供するが、急勾配な学習曲線を受け入れなければならない。MooToolsの拡張性と総合的なアプローチはjQueryの機能を超えるものを提供するが、jQueryが焦点を当てているDOM APIはJavaScriptの継承メソッドを使用したり、MooToolsのようなクラスシステムへとは至らない。
二つのフレームワークのどちらを選んでも素晴らしいと私が言ったのは、以上のような理由だ。コードに見る二つのフレームワークの哲学の違いと長所短所を挙げることに注力した。MooToolsに対する贔屓はできるかぎり排除できたのではないかと思っているが、できればこの比較が役に立つよう願っている。どちらのフレームワークを使うことを選んだとしても、その双方についてよく知るようにしてほしい。時間に余裕があるのならば、その二つをそれぞれサイトに実装してみてほしい。そしてそれぞれのレビューを書いてもらえれば、私が見逃したことを救い出してもらえるだろう。
この文書の履歴はgithubで見ることができる。
翻訳者による感想
僕のJavaScriptライブラリ遍歴はYUI→Spry→MooToolsという、貞淑な乙女の男遍歴のように短いのでよくわかりませんが、MooToolsには非常に期待しています。
ActionScriptがだんだん高機能になって、単なるWebインターフェースの改善から高機能アプリケーション開発ツールへと変貌しつつある現在、MooToolsがJavaScriptにとってそうなってくれると嬉しいですね。
ただ、プラグインの公式リポジトリは欲しいなあ。