jQuery插件开发的五种形态小结
关于jQuery插件的开发自己也做了少许研究,自己也写过多个插件,在自己的团队了也分享过一次关于插件的课。开始的时候整觉的很复杂的代码,现在再次看的时候就清晰了许多。这里我把我自己总结出来的东西分享出来,帮助那些和我一样曾经遇到过同样问题的人。
我要做什么
我想要得到的javascript插件应该会有以下几个特征
代码相对独立
链式操作
插件可配置
有可操作的方法,插件的生命周期可控制
配置可被缓存
可扩展
无冲突处理
事件代理,动态初始化
*以下的代码均假设存在jQuery
插件的第一形态
面对这种情况,通常我们会通过定义function的方式来实现。
functionpluginName($selector){
$.each($selector,function(){
$(this).css("background-color","#ccc");
//todosomething...
});
}
//pluginName(document.getElementsByClassName("demo"));
因为我谈的是jQuery插件开发,那么我现在把这段代码扩展到jQuery上,代码如下:
//IIFE(立即调用函数表达式); [参考http://suqing.iteye.com/blog/1981591/]
;(function($){
//扩展这个方法到jQuery.
//$.extend()是吧方法扩展到$对象上,和$.fn.extend不同。扩展到$.fn.xxx上后,
//调用的时候就可以是$(selector).xxx()
$.fn.extend({
//插件名字
pluginName:function(){
//遍历匹配元素的集合
//注意这里有个"return",作用是把处理后的对象返回,实现链式操作
returnthis.each(function(){
//在这里编写相应的代码进行处理
});
}
});
//传递jQuery到内层作用域去,如果window,document用的多的话,也可以在这里传进去.
//})(jQuery,window,document,undefined);
})(jQuery,undefined);
//调用方式$(".selector").pluginName().otherMethod();
但是还差的远,目前只解决了两个问题
代码相对独立插件可配置
链式操作
有可操作的方法,插件的生命周期可控制
配置可被缓存
可扩展
无冲突处理
事件代理,动态初始化
插件的第二形态
现在来给插件添加参数支持。代码如下
;(function($){
$.fn.pluginName=function(options){
//合并参数,通过“extend”合并默认参数和自定义参数
varargs=$.extend({},$.fn.pluginName.defaults,options);
returnthis.each(function(){
console.log(args.text);
//todosomething...
});
};
//默认参数
$.fn.pluginName.defaults={
text:"hello"
};
})(jQuery);
//$(".selector").pluginName({
// text:"helloworld!"
//});
添加参数支持还比较容易些,又解决一问题
代码相对独立有可操作的方法,插件的生命周期可控制
链式操作
插件可配置
配置可被缓存
可扩展
无冲突处理
事件代理,动态初始化
插件的第三形态
现在来添加方法的支持,我前面所提到的生命周期可控制,意思差不多,例如添加reInit,destory等方法来控制插件。
;(function($){
$.fn.pluginName=function(method){
//如果第一个参数是字符串,就查找是否存在该方法,找到就调用;如果是object对象,就调用init方法;.
if(methods[method]){
//如果存在该方法就调用该方法
//apply是吧obj.method(arg1,arg2,arg3)转换成method(obj,[arg1,arg2,arg3])的过程.
//Array.prototype.slice.call(arguments,1)是把方法的参数转换成数组.
returnmethods[method].apply(this,Array.prototype.slice.call(arguments,1));
}elseif(typeofmethod==='object'||!method){
//如果传进来的参数是"{...}",就认为是初始化操作.
returnmethods.init.apply(this,arguments);
}else{
$.error('Method'+method+'doesnotexistonjQuery.pluginName');
}
};
//不把方法扩展在$.fn.pluginName上.在闭包内建个"methods"来保存方法,类似共有方法.
varmethods={
/**
*初始化方法
*@param_options
*@return{*}
*/
init:function(_options){
returnthis.each(function(){
var$this=$(this);
varargs=$.extend({},$.fn.pluginName.defaults,_options);
//...
})
},
publicMethod:function(){
private_methods.demoMethod();
}
};
//私有方法
functionprivate_methods={
demoMethod:function(){}
}
//默认参数
$.fn.pluginName.defaults={
};
})(jQuery);
//调用方式
//$("div").pluginName({...}); //初始化
//$("div").pluginName("publicMethod"); //调用方法
又解决一问题
代码相对独立配置可被缓存
链式操作
插件可配置
有可操作的方法,插件的生命周期可控制
可扩展
无冲突处理
事件代理,动态初始化
插件的第四形态
第三形态的插件修改就已经可以应对大多数插件的需求了。精益求精嘛,继续升级。
第四形态的插件是照帮司徒正美的《javascript框架设计》的代码。加了点面向对象的知识。
(function($){
varPlugin=function(element,options){
this.element=element;
this.options=options;
};
Plugin.prototype={
create:function(){
console.log(this.element);
console.log(this.options);
}
};
$.fn.pluginName=function(options){
//合并参数
returnthis.each(function(){
//在这里编写相应的代码进行处理
varui=$._data(this,"pluginName");
//如果该元素没有初始化过(可能是新添加的元素),就初始化它.
if(!ui){
varopts=$.extend(true,{},$.fn.pluginName.defaults,typeofoptions==="object"?options:{});
ui=newPlugin(this,opts);
//缓存插件
$._data(this,"pluginName",ui);
}
//调用方法
if(typeofoptions==="string"&&typeofui[options]=="function"){
//执行插件的方法
ui[options].apply(ui,args);
}
});
};
$.fn.pluginName.defaults={};
})(jQuery);
//调用的方式和之前一样。
这里特别要提下缓存这个东西,插件用多了,觉的这个真的是好东西。
在传统面向对象的插件开发中,至少会声明个变量保存它,但是我到目前写的jQuery插件中都没有,用起来很麻烦。自从把初始化后的插件缓存起来后,方便了许多。通过代码$("#target").data("pluginName")就可以取到对象了。来看看还有什么问题没有解决
代码相对独立可扩展
链式操作
插件可配置
有可操作的方法,插件的生命周期可控制
配置可被缓存
无冲突处理
事件代理,动态初始化
插件的第五形态
看了上面的代码是否脑子有点晕了,如果是,休息片刻,稍后回来,下面的代码更精彩。最后一个方案算是比较全面的了。方案来自Bootstrap,下面代码以Bootstrap的button插件为例.
!function($){
//ecma262v5的新东西,强制使用严谨的代码编写.
"usestrict";
//BUTTONPUBLICCLASSDEFINITION
//==============================
varButton=function(element,options){
this.$element=$(element);
this.options=$.extend({},Button.DEFAULTS,options);
};
Button.DEFAULTS={
loadingText:'loading...'
};
Button.prototype.setState=function(state){
//...
};
Button.prototype.toggle=function(){
//...
};
//BUTTONPLUGINDEFINITION
//========================
varold=$.fn.button;//这里的$.fn.button有可能是之前已经有定义过的插件,在这里做无冲突处理使用。
$.fn.button=function(option){
returnthis.each(function(){
var$this=$(this);
//判断是否初始化过的依据
vardata=$this.data('bs.button');
varoptions=typeofoption=='object'&&option;
//如果没有初始化过,就初始化它
if(!data)$this.data('bs.button',(data=newButton(this,options)));
if(option=='toggle')data.toggle();
elseif(option)data.setState(option)
})
};
//①暴露类名,可以通过这个为插件做自定义扩展
$.fn.button.Constructor=Button;
//扩展的方式
//设置:$.fn.button.Constructor.newMethod=function(){}
//使用:$btn.button("newMethod");
//②无冲突处理
$.fn.button.noConflict=function(){
$.fn.button=old;
returnthis
};
//③事件代理,智能初始化
$(document).on('click.bs.button.data-api','[data-toggle^=button]',function(e){
var$btn=$(e.target);
//查找要初始化的对象
if(!$btn.hasClass('btn'))$btn=$btn.closest('.btn');
//直接调用方法,如果没有初始化,内部会先进行初始化
$btn.button('toggle');
e.preventDefault();
});
}(jQuery);
来看看还有什么问题没有解决
代码相对独立
链式操作
插件可配置
有可操作的方法,插件的生命周期可控制
配置可被缓存
可扩展
无冲突处理
事件代理,动态初始化
补充
现在的插件都要求灵活性要高,比如希望插件可以同时适配jQuery和Zepto,又或者需要支持AMD或者CMD规范。
支持jQuery和Zepto
if(window.jQuery||window.Zepto){
(function($){
//plugincode...
})(window.jQuery||window.Zepto);
}
中间件支持,node
if(typeof(module)!=='undefined')
{
module.exports=pluginName;
}
requirejs(AMD)support
if(typeofdefine==='function'&&define.amd){
define([],function(){
'usestrict';
returnpluginName;
});
}
seajs(CMD)support
if(typeofdefine==='function'){
define([],function(){
'usestrict';
returnpluginName;
});
}
呼~,问题都解决了,代码若有看不懂的地方可以多看看。后面的几个看不懂也没有关系,在实际的开发中,前面几个够用了。要强调下,并不是越高级的写法越好,要看自己项目的需求合理的选择。
好了,今天的总结就先到这里了,如果大家有更好的插件开发方式,还请告知一下。希望大家能够喜欢本文。