如何写一个jQuery插件
jQuery 插件开发模式
jQuery 的插件开发模式主要有三种:
- 通过$.extend()来扩展jQuery
- 通过$.fn向jQuery添加新的方法
- 通过$.widget()应用jQuery UI 的部件工厂方式创建
这里我们选用第二种:
$.fn.myplugin = functin() {
// plugin code
}
因为这种方法是加在jQuery对象上,可以在jQuery选择器选择元素后直接调用:
$('body').myplugin();
若对其他两种方法有兴趣,请自行查看jQuery 官方文档。
一个非常简单的例子
改变元素的背景颜色插件:
$.fn.tinyPlugin = function(){
this.css('background-color','#fff'); // 这里的this是jQuery对象,而不是原生的js对象
}
这样写的话,颜色不能使用时定义,修改一下:
$.fn.tinyPlugin = function(bg){
this.css('background-color',bg);
}
以上是一个jQuery插件的基本结构。但编写更复杂的jQuery插件,借助面向对象的思想可以使插件具有更好的扩展性、可维护性。
面向对象开发插件
大体思路如下:
1.创建myplugin构造函数
2.定义myplugin的方法
3.添加到jQuery对象中
基本结构
首先将代码放到一个自执行的匿名函数中,防止全局对象污染:
根据上面的步骤,基本结构大致如下:
// 定义构造函数
function myplugin($element,options){
// ...
}
// 添加方法
myplugin.prototype = {
method1: function(){
// ...
},
method2: function(){
// ...
}
};
// 添加到jQuery对象中
$.fn.myplugin = function(options){
new myplugin(this,options);
}
后面将在这个结构上进行完善:
放到立即执行的匿名函数内部
放到匿名函数内部的主要作用是隔离作用域,避免变量污染,而自执行可以返回需要的函数或对象,而不需要每次通过条件判断为处理,只需要在首次加载的时候赋值给某个变量。更详细的信息参见知乎上的 这个问题。
;(function($){
// ...
})(jQuery);
留意到上面,函数前还加了分号,这是由于自执行函数前省略分号的表达式时,某些情况下会报错,可以看 这里。
在匿名函数里面还传入了jQuery对象,这样做的好处是美元符号 $
在内部是一个私有变量,仅代表jQuery,可以防止和其他使用 $
的库冲突。
有的自执行函数还将window对象、undefined对象传入,具体作用可以看 这里。
默认参数
options是提供给使用者定义的参数。一个好的插件,应该提供必要的参数给使用者,并且提供好默认的参数,因为很多情况下不是每个参数都需要自定义。之前在项目中写了一个加载更多 LoadMore
插件,就以这个插件为例:
function LoadMore($element,options){
this.$element = $element;
this._defaults = {
loadingSelector: ".zc_loading", //加载中的提示
overSelector: ".zc_loaded", // 加载完毕的加载
}
this.options = $.extend({},this._defaults,options||{}); // 合并参数
}
初始化方法
一般插件都会有一个 init()
方法,调用插件时做一些初始化操作:
function myplugin($element,options){
this.init();
}
// 添加方法
myplugin.prototype = {
init: function(){
// ...
}
};
防止多次初始化
为了防止用户多次在同一个元素上调用插件,有必要加一些已初始化的判断。通过在该元素上用 data()
方法缓存插件的实例对象,每次实例化时判断该缓存对象即可。
$.fn.myplugin = function(options){
if (!$.data(this, 'myplugin')) {
$.data(this, 'myplugin', new myplugin(this, options));
}
}
遍历所有元素
$
选择器返回的有可能是多个元素,因此需要返回的所有元素添加插件,直接使用jQuery自带的 each()
方法:
$.fn.myplugin = function(options){
this.each(function(){
if (!$.data(this, 'myplugin')) {
$.data(this, 'myplugin', new myplugin(this, options));
}
});
}
注意这里的this
是jQuery对象,而不是原生JS对象。所以可以直接使用this.each()
。
保持链式操作
由于jQuery有个链式操作的特性,在每个方法调用后都会返回该对象,因此只需要 return
该对象即可保持链式操作:
$.fn.myplugin = function(options){
return this.each(function(){
// ...
});
}
给插件添加方法
插件不仅有可配置的参数,很多时候还需要调用方法。比如说“加载更多”插件,可能需要知道现在加载到第几页,或者是否已经全部加载完毕。此时就需要调用方法来获得结果。
在前面我们知道是在 prototype
定义方法:
LoadMore.prototype = {
init: function(){},
// 重新加载
reload: function(){},
// 开始加载
startLoad: function(){},
// 本次加载完成
loadOver: function(){},
// 全部加载完毕
allLoadOver: function(){},
// 插入内容
insert: function(content){}
}
方法名可以通过options参数传入,只需在内部做字符串判断:
$.fn.LoadMore = function(options,para){
var instance;
instance = $.data(this,'LoadMore');
if(!instance){
instance = new LoadMore(this,options);
$.data(this,'LoadMore',instance);
}
// 如果是字符串则调用方法
if($.type(options) === 'string') {
return instance[options](para);
}
return this; // 保持链式
}
因此我们调用方法可以像下面这样:
$('#list').LoadMore('startLoad');
但是,当调用的方法需要传参怎么办?很简单,那就再加一个参数 para
:
$.fn.LoadMore = function(options,para){
// ...
// 如果是字符串则调用方法
if($.type(options) === 'string') {
return instance[options](para);
}
return this; // 保持链式
}
调用带参数的方法:
$('#list').LoadMore('insert','这是待插入的内容');
这里如果要使用回调函数,和上面也是一样的道理,只需在内部加个函数判断。不详述。若有更多的参数,请使用对象 {}
。
配置参数中添加事件
在写 LoadMore
插件的过程中,还发现需要使用事件,比如说滚动到某个位置时,执行某些操作。也很简单,事件我们可以直接写在配置参数 options 中:
this._defaults = {
onScroll: null, // scroll中
onScrollBottom: null // scroll到底部
}
然后在内部 init()
函数中调用前判断:
init: function(){
var that = this,
$win = $(window),
$doc = $(document);
$win.on('scroll',function(){
(typeof that.options['onScroll'] == 'function') && that.options['onScroll']();
if($win.scrollTop() + $win.height() >= $doc.height()-10){
(typeof that.options['onScrollBottom'] == 'function') && that.options['onScrollBottom']();
}
});
}
兼容AMD规范
现在流行模块化开发,AMD是require.js在推广过程中的规范化产出,AMD说明见 这里。
下面的代码可以使你的插件兼容AMD规范,可以在require.js中直接调用,当然,没有使用require.js直接 <script>
引入也是可以的。
// amd support
(function(factory){
// amd support
if(typeof define === 'function' && define.amd){
define(['jquery'],factory);
}else {
factory(jQuery);
}
}(function($){
var exports = {};
// 这里写插件代码
return exports;
}));
上面的前一段是判断是否有define函数,有则调用define方法定义一个模块,否则直接执行。后一段的 exports
对象是模块的返回对象,模块对外的接口。关于AMD规范的具体信息,可参考阮一峰的 这篇文章。
结语
上面所写的就是写一个插件的大致流程,由于能力有限,还有很多要完善的地方。想要了解更多,这里推荐一篇文章 深入理解jQuery插件开发。另外,这里提供一个gitHub上面的某位大神写的插件 SlipHover供参考。