jQuery プラグインの定義パターンについて調べてみた
jQuery のプラグインの定義手法については、いろいろな記述の仕方が考えられるかと思います。今回、実際に公開されているソースを参照して、どのような定義パターンがあるのかを調べてみました。
jQuery 標準 API の定義構造のおさらい
まず、jQuery が標準で提供している各種 API が、どうのような構造で定義されてるかおさらいしてみます。
$ や jQuery はグローバル変数、つまり window オブジェクトのプロパティ名であり、その実体は関数オブジェクトです。
window.$ = window.jQuery = function(){ ... }
jQuery が提供する API には 関数 API と メソッド API があり、関数 API は jQuery 関数オブジェクト(以降 jQuery セレクタと記述します)が持つ、関数オブジェクトのことを指します。
jQuery.ajax = function(){ ... } //関数 API
メソッド API は、jQuery セレクタの prototype オブジェクトが持つ関数オブジェクトのことを指します。prototype は全ての関数オブジェクトが持っているオブジェクトで、プラグイン定義の際に使用される jQuery.fn は jQuery.prototype の別名(ショートカット)です。
jQuery.prototype.show = function(){ ... } //メソッド API jQuery.fn = jQuery.prototype;
jQuery セレクタで要素を取得すると、jQuery セレクタを new することで生成される jQuery インスタンスが返されます。jQuery インスタンスは、jQuery.prototype に定義された関数郡を、自身が保有するメソッドのように扱うことができます。
var instance = new jQuery(); instance.show(); // jQuery.prototype に定義された関数がメソッドとして使える
jQuery にプラグインを定義するという行為は、これら関数 API 、メソッド API を独自に定義することを意味します。
では実際に、どのようなかたちでプラグインが実装されてるか見てみます。
1. 関数 API 系のプラグイン
1.2 複数の関数を定義
(function($){ $.plugin = { hoge : function(){ //... }, fuga : function(){ //... } } })(jQuery);
2. メソッド API 系のプラグイン
2.1 標準的な定義
(function($){ $.fn.myPlugin = function( option ) { return this.each(function() { ... init var hoge = function(){ ... } var fuga = function(){ ... } }); }; })(jQuery);
プラグイン作成の入門記事で紹介されてるような標準的な定義(だと思うのですが該当するプラグインがあまり見つかりませんでした)。プラグイン実行の度、サブメソッド(hoge , fuga)が再定義されるので無駄な処理コストが発生します。
2.2 実装独立型の定義
(function($){ // var myPlugin = function( elem , option) { // private $.myPlugin = function( elem , option) { // public // init var hoge = function(){ ... } var fuga = function(){ ... } } $.fn.myPlugin = function( option ) { return this.each(function() { $.myPlugin( this , option); }); }; })(jQuery);
インターフェース部と実装部を分離した書き方です。インデントが浅くなることと、実装定義の外部参照が可能になること意外は「2.1 標準的な定義」と大差ありません。
2.3 サブメソッドの外部定義
(function($){ // var myPlugin = { // private $.myPlugin = { // public hoge : function(){ ... }, fuga : function(){ ... } } $.fn.myPlugin = function( option ) { return this.each(function() { // init }); }; })(jQuery);
サブメソッド(hoge , fuga)をプラグインメソッド外で定義してるので、プラグイン実行時のサブメソッドの再定義コストは無くなりますが、サブメソッド実行時に基本パラメータ( option )の受け渡しを考慮する必要があります。
2.4 サブメソッドをプラグインメソッド直下に定義
(function($){ // var myPlugin = function( option ) { // private $.fn.myPlugin = function( option ) { // public return this.each(function() { // init }); }; $.extend($.fn.myPlugin , { hoge : function(){ ... }, fuga : function(){ ... } }); })(jQuery);
「2.3 サブメソッドの外部定義」とほぼ同じですが、サブメソッドをプラグインメソッド( $.fn.myPlugin )の直下に定義してるので、jQuery セレクタは汚染されません。
2.5 サブメソッドをプライベート関数として定義
(function($){ var hoge = function(){ ... } var fuga = function(){ ... } $.fn.myPlugin = function( option ) { return this.each(function() { // init }); }; })(jQuery);
サブメソッドをプライベート関数として定義するので、プラグイン外部から参照できなくなります。
2.6 関数 API / メソッド API の同時定義
(function($){ $.myPlugin = function( option ){ $.myPlugin.init( window , option); } $.extend($.myPlugin,{ init : function( elem , option ){ // init }, hoge : function(){ ... }, fuga : function(){ ... } }); $.fn.myPlugin = function( option ) { return this.each(function() { $.myPlugin.init( this , option); }); }; })(jQuery);
2.7 実装を prototype オブジェクトで定義
(function($){ $.myPlugin = function( elem , option ){ // init } $.extend( $.myPlugin.prototype,{ hoge : function(){ ... }, fuga : function(){ ... } }); $.fn.myPlugin = function( option ) { return this.each(function() { new $.myPlugin( this , option); }); }; })(jQuery);
prototype ベースオブジェクト指向による JavaScript らしい実装(だと思う)。jQuery セレクタで取得された要素1つ1つが、専用のプラグインインスタンスを持つことになるで、各要素固有のパラメータをインスタンス側で保持することができます。jQuery UI ではこのインスタンスを data() メソッドにより対応する要素にもたせることで、プラグイン利用者がインスタンスの存在を意識することなく、自由なタイミングでサブメソッド( hoge や fuga )を実行できる API を提供してます。
ちなみ 実際の jQuery UI のソースでは、$.widget という関数を使用して動的に上記のようなプラグインの定義を行っています。
2.8 new を使った定義
(function($){ $.myPlugin = new function(){ var hoge = function(){ ... }, var fuga = function(){ ... } this.init = function( option ) { return this.each(function() { // init } } } $.fn.myPlugin = $.myPlugin.init; })(jQuery);
プラグイン定義のタイミングで new することで実行される無名関数内で、外部参照可能なプラグインメソッド( this.init )を定義した後 $.fn に割り当ててます。
2.9 名前競合の懸念のある定義
(function($){ $.extend( $.fn,{ myPlugin : function( option ){ // init }, hoge : function(){ ... }, fuga : function(){ ... } }); })(jQuery);
プラグイン名としてうたってる名前以外のメソッド(hoge , fuga)も、プラグインとして定義しちゃってます(以下参照)。名前競合しないようなネーミングの工夫が必要かと思われます。
Treeview
- $.fn.swapClass
- $.fn.replaceClass
- $.fn.hoverClass
- $.fn.heightToggle
- $.fn.heightHide
- $.fn.prepareBranches
- $.fn.applyClasses
flexigrid
- $.fn.flexReload
- $.fn.flexOptions
- $.fn.flexToggleCol
- $.fn.flexAddData
- $.fn.noSelect
jTemplate
- $.fn.setTemplate
- $.fn.setTemplateURL
- $.fn.setTemplateElement
- $.fn.hasTemplate
- $.fn.removeTemplate
- $.fn.setParam
Easy Drag
- $.fn.ondrag
- $.fn.ondrop
- $.fn.dragOff
- $.fn.dragOn
結局どの定義パターンがいいのか?
個人的な好みになりますが...
- ちょっとした処理なら「2.1 標準的な定義」
(function($){ $.fn.myPlugin = function( option ) { return this.each(function() { // init var hoge = function(){ ... } var fuga = function(){ ... } }); }; })(jQuery);
- そこそこの処理だけど、要素個別のデータ保持やサブメソッドの外部実行が不要なら「2.3 サブメソッドの外部定義」(継承等も考慮するなら init 処理は $.myPlugin に書いた方がいいかも..)
(function($){ // var myPlugin = { // private $.myPlugin = { // public hoge : function(){ ... }, fuga : function(){ ... } } $.fn.myPlugin = function( option ) { return this.each(function() { // init }); }; })(jQuery);
- それ以外は「2.7 実装を prototype オブジェクトで定義」
(function($){ $.myPlugin = function( elem , option ){ // init } $.extend( $.myPlugin.prototype,{ hoge : function(){ ... }, fuga : function(){ ... } }); $.fn.myPlugin = function( option ) { return this.each(function() { new $.myPlugin( this , option); }); }; })(jQuery);
といったところでしょうか。