この記事では、jQuery プラグイン開発の詳細を例を通して紹介します。まず、jQuery プラグイン開発の基礎知識を紹介します。
jQuery プラグインの開発は主に 2 つのカテゴリに分かれています:
1. クラス レベル。つまり、$.ajax、$.get などの jQuery クラス自体のメソッドを拡張します。
2. オブジェクト レベル。ここでいうオブジェクトとは、jQuery セレクターを通じて選択された jQuery オブジェクトを指します。例: $('div').css()、$('div').show() など。
実際の開発では、通常、オブジェクト レベルの方法を使用してプラグインを開発します。これが、この方法を選択する大きな理由です。
次に、2 つのメソッドの具体的な記述方法を見てみましょう。
クラスレベルのプラグイン開発
$.extend({ foo: function() { //... }, bar: function() { //... } }) //调用 $.foo();
ここで、拡張メソッドの名前は、jQuery クラスの元のメソッドと同じ名前にならないように、さらに注意する必要があります。それでも、クラスの複数のメソッドを拡張する必要がある場合には、名前の競合が依然として発生する可能性があります。カスタム名前空間を作成できるためです:
$.myPlugin = { foo: function() { //... }, bar: function() { //... } } //调用 $.myPulgin.foo();
オブジェクトレベルのプラグイン開発
$.fn.foo = function() { //doSomething... } //调用(假设拥有一个id为obj的元素) $('#obj').foo(); 有个会问 fn 是什么东东?粘一段别人截取的jQuery源码就明白了: jQuery.fn = jQuery.prototype = { init: function(selector, context) { //.... } }
それはプロトタイプのチェーンであることが判明しました。 。 。
設定パラメータの受信
プラグインを作成するときに、プラグインを使用するユーザーがプラグインのいくつかのプロパティを自由に設定できるようにするには、プラグインにパラメーターを受け取る機能が必要です。プラグインの使用者がパラメータを渡さない場合、プラグインは内部的にデフォルトの設定パラメータのセットも存在します。
$.fn.foo = function(options) { var defaults = { color: '#000', backgroundColor: 'red' }; var opts = $.extend({}, defaults, options); alert(opts.backgroundColor); //yellow } $('#obj').foo({ backgroundColor: 'yellow' })
ここで重要なのは、オブジェクトをマージできる $.extend メソッドです。同じプロパティの場合、後続のオブジェクトは前のオブジェクトを上書きします。 extend メソッドの最初のパラメータが空のオブジェクトなのはなぜですか?このメソッドは後者を前者にマージするため、デフォルトが変更されないように最初のパラメーターは空のオブジェクトに設定されます。
プラグインを使用しているユーザーにデフォルトパラメータの設定を許可する場合は、それらを公開する必要があります:
$.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); alert(opts.backgroundColor); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' }
このようにして、プラグインのデフォルトパラメータを外部から変更できます。
いくつかのメソッドを適切に公開します
$.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); $.fn.foo.sayColor(opts.backgroundColor); } $.fn.foo.sayColor = function(bgColor) { alert(bgColor); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' }
書き直されました:
$.fn.foo.sayColor = function(bgColor) { alert('background color is ' + bgColor); }
プラグインでいくつかのメソッドを公開することは非常に優れており、他の人がメソッドを拡張したりオーバーライドしたりできるようになります。しかし、他の人があなたのパラメータやメソッドを変更すると、他の多くのものに影響を与える可能性があります。したがって、メソッドを公開するかどうかを慎重に検討する必要があります。確信が持てない場合は、公開しないでください。
機能を非公開にする
物事をプライベートに保つということになると、最初に何が思い浮かびますか?そうです、これでクロージャです:
;(function($) { $.fn.foo = function(options) { var opts = $.extend({}, $.fn.foo.defaults, options); debug(opts.backgroundColor); } function debug(bgColors) { console.log(bgColors); } $.fn.foo.defaults = { color: '#000', backgroundColor: 'red' } })(jQuery)
これは jQuery によって提供される公式のプラグイン開発方法であり、次のような利点があります。 1. グローバルな依存関係がない 2. 他者による損傷を回避する 3. '$' および 'jQuery' 演算子と互換性がある。
上記のように、デバッグ メソッドはプラグイン内のプライベート メソッドとなり、外部から変更することはできません。クロージャの前に ; を追加すると、コードのマージ中にクロージャの前のコードにセミコロンが欠けている場合に、後でエラーが報告されるのを防ぎます。
マージ
;(function($) { //定义插件 $.fn.foo = function(options) { //doSomething... } //私有函数 function debug() { //doSomething... } //定义暴露函数 $.fn.foo.sayColor = function() { //doSomething... } //插件默认参数 $.fn.foo.default = { color: '#000', backgroundColor: 'red' } })(jQuery);
上記のコードは、完全で標準化されたプラグインのスケルトンを作成します。非常に簡単に見えますが、実際の開発ではまだ多くのスキルと注意事項があります。例を通して見てみましょう。
長い間考えた結果、例としてポップアップウィンドウをプラグイン化する方が適切だと感じました。開発する前に、まずこのポップアップ プラグインの構造と機能を想像してみましょう:
上の図から、タイトル、コンテンツ、ボタン グループの 3 つの部分で構成されていることがわかります。ここで、ブラウザに 1 つのボタンだけを含むデフォルトのアラート ボックスを作成するのではなく、ユーザーがボタンの数をカスタマイズできるため、ポップアップ ボックスでも同様の機能を実行できることを明記する必要があります。ボックスを確認します。
プラグイン スケルトンを構築する
function SubType($ele, options) { this.$ele = $ele; this.opts = $.extend({}, $.fn.popWin.defaults, options); } SubType.prototype = { createPopWin: function() { } }; $.fn.popWin = function(options) { //this指向被jQuery选择器选中的对象 var superType = new SubType(this, options); superType.createPopWin(); }; $.fn.popWin.defaults = {};
1. PopWin という名前のオブジェクトベースのメソッドを作成し、ユーザーが変更できるようにデフォルトの構成パラメーターを公開しました。
2. ここでは、オブジェクト指向メソッドを使用してプライベート関数を管理します。createPopWin メソッドは、ポップアップ ウィンドウの作成に使用されます。3. プラグインが呼び出されるときに、jq オブジェクトとカスタム パラメーターをコンストラクターに渡し、インスタンス化します。
は を呼び出します
このプラグインをどのように呼ぶか想像してみてください。ドキュメント ツリー内の適切な場所に div 要素を挿入し、div を選択して、jQuery オブジェクトで定義した PopWin メソッドを呼び出すことができます。
$('#content').popWin({ a: 1, b: 2, callback: function() {} });
デフォルト構成を決定する
$.fn.popWin.defaults = { width: '600', //弹窗宽 height: '250', //弹窗高 title: '标题', //标题 desc: '描述', //描述 winCssName: 'pop-win', //弹窗的CSS类名 titleCssName: 'pop-title', //标题区域的CSS类名 descCssName: 'pop-desc', //描述区域的CSS类名 btnAreaCssName: 'pop-btn-box', //按钮区域的CSS类名 btnCssName: 'pop-btn', //单个按钮的CSS类名 btnArr: ['确定'], //按钮组 callback: function(){} //点击按钮之后的回调函数 }
我们定义了如上的参数,为什么有要传入这么多的CSS类名呢?1. 为了保证JS与CSS尽可能的解耦。 2. 你的样式有很大可能别人并不适用。所以你需要配置一份样式表文件来对应你的默认类名,当别人需要更改样式时可以传入自己编写的样式。
按钮组为一个数组,我们的弹窗需要根据其传入的数组长度来动态的生成若干个按钮。回调函数的作用是在用户点击了某个按钮时返回他所点击按钮的索引值,方便他进行后续的操作。
弹窗DOM创建
var popWinDom,titleAreaDom,descAreaDom,btnAreaDom; SubType.prototype = { createPopWin: function() { var _this = this; //首次创建弹窗 //背景填充整个窗口 this.$ele.css({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)', overflow: 'hidden' }); //窗口区域 popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName); //标题区域 titleAreaDom = popWinDom.find('div:eq(0)') .text(this.opts.title) .attr('class',this.opts.titleCssName); //描述区域 descAreaDom = popWinDom.find('div:eq(1)') .text(this.opts.desc) .attr('class',this.opts.descCssName); //按钮区域 btnAreaDom = popWinDom.find('div:eq(2)') .attr('class',this.opts.btnAreaCssName); //插入按钮 this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr({'data-index':index, 'class':_this.opts.btnCssName}) .on('click', function() { _this.opts.callback($(this).attr('data-index')); })); }); this.$ele.append(popWinDom); } }
1. 首先命名了四个变量用来缓存我们将要创建的四个DOM,将传入的jQuery对象变形成覆盖整个窗口半透明元素;
2. 创建窗口DOM,根据传入的高、宽来设置尺寸并居中,之后另上传入的窗口CSS类名;
3. 创建标题、描述、按钮组区域,并将传入的标题、描述内容配置上去;
4. 动态加入按钮,并为按钮加上data-index的索引值。注册点击事件,点击后调用传入的回调函数,将索引值传回。
好了,我们先看下效果。调用如下:
$('#content').popWin({ width: '500', height: '200', title: '系统提示', desc: '注册成功', btnArr: ['关闭'], callback: function(clickIndex) { console.log(clickIndex); } });
可以看到一个弹窗的DOM已被渲染到页面中了,当点击关闭按钮时控制台会打印出 "0",因为按钮组只有一个值嘛,当然是第0个了。
如果我们需要多次调用这个弹窗,每次都要传入高、宽我会觉得很麻烦。这时我们可以直接在一开始修改插件内部的默认配置,这也是我们将默认配置暴露的好处:
$.fn.popWin.defaults.width = '500'; $.fn.popWin.defaults.height = '200';
要注意的当然是不能直接改变defaults的引用,以免露掉必须的参数。 这样以后的调用都无需传入尺寸了。
我们加一个按钮并且传入一个自定义的样式看看好使不呢?
$('#content').popWin({ title: '系统提示', desc: '是否删除当前内容', btnArr: ['确定','取消'], winCssName: 'pop-win-red', callback: function(clickIndex) { console.log(clickIndex); } });
可以看到都是生效了的,当点击“确定”按钮时回调函数返回 0,点击“取消”按钮时回调函数返回 1。这样使用插件的人就知道自己点击的是哪一个按钮,以完成接下来的操作。
显示&隐藏
接下来要进行打开、关闭弹窗功能的开发。回想上面介绍的概念,我们想让使用该插件的人能够对这两个方法进行扩展或者重写,所以将这两个方法暴露出去:
$.fn.popWin.show = function($ele) { $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.hide(); }
之后在createPopWin方法中需要的地方调用这两个方法。
这里多强调一点,也是做弹窗控件不可避免的一点:只有当我们点击按钮以及灰色背景区域时允许弹窗关闭,点击弹窗其他地方不允许关闭。由于弹窗属于整个灰色区域的子节点,必然牵扯到的就是事件冒泡的问题。
所以在给最外层加上点击关闭的事件时,要在弹窗区域阻止事件冒泡。
popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName).on('click', function(event) { event.stopPropagation(); });
二次打开
我们只需要在第一次调用插件时创建所有创建DOM,第二次调用时只更改其参数即可,所以在createPopWin方法最前面加入如下方法:
if (popWinDom) { //弹窗已创建 popWinDom.css({ width: this.opts.width, height: this.opts.height }).attr('class',this.opts.winCssName); titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName); descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName); btnAreaDom.html('').attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr('data-index',index) .attr('class',_this.opts.btnCssName) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); $.fn.popWin.show(this.$ele); return; }
合并整个插件代码
;(function($) { function SubType(ele, options) { this.$ele = ele; this.opts = $.extend({}, $.fn.popWin.defaults, options); } var popWinDom,titleAreaDom,descAreaDom,btnAreaDom; SubType.prototype = { createPopWin: function() { var _this = this; if (popWinDom) { //弹窗已创建 popWinDom.css({ width: this.opts.width, height: this.opts.height }).attr('class',this.opts.winCssName); titleAreaDom.text(this.opts.title).attr('class',this.opts.titleCssName); descAreaDom.text(this.opts.desc).attr('class',this.opts.descCssName); btnAreaDom.html('').attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr('data-index',index) .attr('class',_this.opts.btnCssName) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); $.fn.popWin.show(this.$ele); return; } //首次创建弹窗 this.$ele.css({ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.4)', overflow: 'hidden', display: 'none' }).on('click', function() { $.fn.popWin.hide(_this.$ele); }); popWinDom = $('<div><div></div><div></div><div></div></div>').css({ width: this.opts.width, height: this.opts.height, position: 'absolute', top: '30%', left: '50%', marginLeft: '-' + (this.opts.width.split('px')[0] / 2) + 'px' }).attr('class',this.opts.winCssName).on('click', function(event) { event.stopPropagation(); }); titleAreaDom = popWinDom.find('div:eq(0)') .text(this.opts.title) .attr('class',this.opts.titleCssName); descAreaDom = popWinDom.find('div:eq(1)') .text(this.opts.desc) .attr('class',this.opts.descCssName); btnAreaDom = popWinDom.find('div:eq(2)') .attr('class',this.opts.btnAreaCssName); this.opts.btnArr.map(function(item, index) { btnAreaDom.append($('<button></button>') .text(item) .attr({'data-index':index, 'class':_this.opts.btnCssName}) .on('click', function() { _this.opts.callback($(this).attr('data-index')); $.fn.popWin.hide(_this.$ele); })); }); this.$ele.append(popWinDom); $.fn.popWin.show(this.$ele); } } $.fn.popWin = function(options) { var superType = new SubType(this, options); superType.createPopWin(); return this; } $.fn.popWin.show = function($ele) { $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.hide(); } $.fn.popWin.defaults = { width: '600', height: '250', title: 'title', desc: 'description', winCssName: 'pop-win', titleCssName: 'pop-title', descCssName: 'pop-desc', btnAreaCssName: 'pop-btn-box', btnCssName: 'pop-btn', btnArr: ['确定'], callback: function(){} } })(jQuery);
如上,一个完整的弹窗插件就在这里了。
说下这个标红的 return this 是干什么用的,前面已说过 this 在这里是被选中的jQuery对象。将其return就可以在调用完我们的插件方法后可以继续调用jQ对象上的其他方法,也就是jQuery的链式操作,说玄乎点就叫级联函数。
OK!趁热打铁,我们来看看暴露出去的两个方法重写之后效果怎么样,毕竟对插件暴露部分的扩展和重写是很牛逼的一块东西。
想象个情景,你用了这个插件后觉得简单的show和hide效果简直是low爆了,决定重写这个弹出和隐藏的效果:
$.fn.popWin.show = function($ele) { $ele.children().first().css('top','-30%').animate({top:'30%'},500); $ele.show(); } $.fn.popWin.hide = function($ele) { $ele.children().first().animate({top:'-30%'},500,function() { $ele.hide(); }); }
你在自己的代码里加上上面两段,然后发现弹窗有了一个简单的上下滑动进入屏幕的效果,同时又不会影响我们弹窗的创建,证明我们的暴露方法还算合理。
当然你也可以让它竖着进、横着进、翻着跟头进,这就看你自己了。
最后贴上默认的样式表,为了急着想粘回去试试的同学们。
.pop-win { border: 1px solid #fff; padding: 10px; background-color: #fff; -wekbit-border-radius: 6px; border-radius: 6px; -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3); box-shadow: 0 3px 9px rgba(0,0,0,0.3); } .pop-win-red { padding: 10px; background-color: red; -wekbit-border-radius: 6px; border-radius: 6px; -webkit-box-shadow: 0 3px 9px rgba(0,0,0,0.3); box-shadow: 0 3px 9px rgba(0,0,0,0.3); } .pop-title { width: 100%; height: 20%; line-height: 40px; padding-left: 10px; box-sizing: border-box; border-bottom: 1px solid #eee; font-size: 17px; font-weight: bold; } .pop-desc { width: 100%; height: 60%; box-sizing: border-box; padding: 10px 0 0 10px; border-bottom: 1px solid #eee; } .pop-btn-box { width: 100%; height: 20%; text-align: right; } .pop-btn { margin: 10px 10px 0 0; width: 60px; height: 30px; }
当然这只是个编写插件的例子,如果要拿出去使用还需要仔细打磨。例子虽然简单,旨在抛砖引玉。