jQuery插件制作的实例教程
一、jQuery插件的类型
1.jQuery方法
很大一部分的jQuery插件都是这种类型,由于此类插件是将对象方法封装起来,在jQuery选择器获取jQuery对象过程中进行操作,从而发挥jQuery强大的选择器优势。
2.全局函数法
可以把自定义的功能函数独立附加到jQuery命名空间下,从而作为jQuery作用域下的一个公共函数使用。
但全局函数没有被绑定到jQuery对象上,故不能在选择器获取的jQuery对象上调用。需要通过jQuery.fn()或$.fn()方式进行引用。
3.选择器法
如果觉得jQuery提供的选择器不够用或不方便的话,可以考虑自定义选择器。
二、jQuery插件的机制
1. jQuery.extend()方法
这种方法能够创建全局函数或选择器。
所谓全局函数,就是jQuery对象的方法,实际上就是位于jQuery命名空间内部的函数,有人把这类函数称为实用工具函数,这些函数都有一个共同特征,就是不直接操作DOM元素,而是操作Javascript的非元素对象,或者执行其他非对象的特定操作,如jQuery的each()函数和noConflict()函数。
例如,在jQuery命名空间上创建两个公共函数:
jQuery.extend({
min:function(a,b){
returna<b?a:b;
},
max:function(a,b){
returna<b?b:a;
}
})
$(function(){
$("input").click(function(){
vara=prompt("请输入一个数:");
varb=prompt("再输入一个数:");
varc=jQuery.min(a,b);
vard=jQuery.max(a,b);
alert("最大值是:"+d+"\n最小值是:"+c);
});
})
<inputtype="button"value="jQuery扩展测试"/>
jQuery.extend({
min:function(a,b){
returna<b?a:b;
},
max:function(a,b){
returna<b?b:a;
}
})
$(function(){
$("input").click(function(){
vara=prompt("请输入一个数:");
varb=prompt("再输入一个数:");
varc=jQuery.min(a,b);
vard=jQuery.max(a,b);
alert("最大值是:"+d+"\n最小值是:"+c);
});
})
<inputtype="button"value="jQuery扩展测试"/>
jQuery.extend()方法除了可以创建插件外,还可以用来扩展jQuery对象。
例如,调用jQuery.extend()方法把对象a和对象b合并为一个新的对象,并返回合并对象将其赋值给变量c:
vara={name:"aaa",pass:777};
varb={name:"bbb",pass:888,age:9};
varc=jQuery.extend(a,b);
$(function(){
for(varnameinc){
$("div").html($("div").html()+"<br/>"+name+":"+c[name]);
}
})
如果要向jQuery命名空间上添加一个函数,只需要将这个新函数制定为jQuery对象的一个属性即可。其中jQuery对象名也可以简写为$,jQuery.smalluv==$.smalluv。
例如,创建jQuery全局函数:
jQuery.smalluv={
min:function(a,b){
returna<b?a:b;
},
max:function(a,b){
returna<b?b:a;
}
}
$(function(){
$("input").click(function(){
vara=prompt("请输入一个数:");
varb=prompt("再输入一个数:");
varc=jQuery.smalluv.min(a,b);
vard=jQuery.smalluv.max(a,b);
alert("最大值是:"+d+"\n最小值是:"+c);
});
})
2.jQuery.fn.extend()方法
这种方法能够创建jQuery对象方法。
举一个最简单的jQuery对象方法例子:
jQuery.fn.test=function(){
alert("jQuery对象方法");
}
$(function(){
$("div").click(function(){
$(this).test();
});
})
三、初步总结
在jQuery匿名函数中,采用jQuery.extend();方法创建jQuery插件
在jQuery匿名函数中,采用对象.属性=函数的方式创建jQuery插件
<!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01Transitional//EN">
<html>
<head>
<title>最简单的jquery插件</title>
<metahttp-equiv="Content-Type"content="text/html;charset=UTF-8">
<scripttype="text/javascript"src="../res/jquery/jquery-1.4.4.min.js"></script>
<scripttype="text/javascript">
(function($){
jQuery.extend({//写法1
a:function(h){
$("#ad").html(h);
},
b:function(h){
alert(h);
}
})
})(jQuery);
(function($){//写法2
jQuery.a=function(h){
$("#ad").html(h);
}
jQuery.b=function(h){
alert(h);
}
})(jQuery);
$(document).ready(function(){
$.a("abc");
$.b("xyz");
});
</script>
</head>
<body>
<h3>最简单的jQuery插件</h3>
<divid="ad"></div>
</body>
</html>
四、编写实例
写法一
插件主体:
(function($,window){
//初始态定义
var_oDialogCollections={};
//插件定义
$.fn.MNDialog=function(_aoConfig){
//默认参数,可被重写
vardefaults={
//string
sId:"",
//num
nWidth:400,
//bollean
bDisplayHeader:true,
//object
oContentHtml:"",
//function
fCloseCallback:null
};
var_oSelf=this,
$this=$(this);
//插件配置
this.oConfig=$.extend(defaults,_aoConfig);
//初始化函数
var_init=function(){
if(_oDialogCollections){
//对于已初始化的处理
//如果此时已经存在弹框,则remove掉再添加新的弹框
}
//初始化弹出框数据
_initData();
//事件绑定
_loadEvent();
//加载内容
_loadContent();
}
//私有函数
var_initData=function(){};
var_loadEvent=function(){};
var_loadContent=function(){
//内容(分字符和函数两种,字符为静态模板,函数为异步请求后组装的模板,会延迟,所以特殊处理)
if($.isFunction(_oSelf.oConfig.oContentHtml)){
_oSelf.oConfig.oContentHtml.call(_oSelf,function(oCallbackHtml){
//便于传带参函数进来并且执行
_oSelf.html(oCallbackHtml);
//有回调函数则执行
_oSelf.oConfig.fLoadedCallback&&_oSelf.oConfig.fLoadedCallback.call(_oSelf,_oSelf._oContainer$);
});
}elseif($.type(_oSelf.oConfig.oContentHtml)==="string"){
_oSelf.html(_oSelf.oConfig.oContentHtml);
_oSelf.oConfig.fLoadedCallback&&_oSelf.oConfig.fLoadedCallback.call(_oSelf,_oSelf._oContainer$);
}else{
console.log("弹出框的内容格式不对,应为function或者string。");
}
};
//内部使用参数
var_oEventAlias={
click:'D_ck',
dblclick:'D_dbl'
};
//提供外部函数
this.close=function(){
_close();
}
//启动插件
_init();
//链式调用
returnthis;
};
//插件结束
})(jQuery,window);
调用
varMNDialog=$("#header").MNDialog({
sId:"#footer",//覆盖默认值
fCloseCallback:dialog,//回调函数
oContentHtml:function(_aoCallback){
_aoCallback(_oEditGrpDlgView.el);
}
}
});
//调用提供的函数
MNDialog.close;
functiondialog(){
}
点评
1.自调用匿名函数
(function($,window){
//jquerycode
})(jQuery,window);
用处:通过定义一个匿名函数,创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏全局的命名空间。这点非常有用也是一个JS框架必须支持的功能,jQuery被应用在成千上万的JavaScript程序中,必须确保jQuery创建的变量不能和导入他的程序所使用的变量发生冲突。
2.匿名函数为什么要传入window
通过传入window变量,使得window由全局变量变为局部变量,当在jQuery代码块中访问window时,不需要将作用域链回退到顶层作用域,这样可以更快的访问window;这还不是关键所在,更重要的是,将window作为参数传入,可以在压缩代码时进行优化,看看jquery.min.js:
(function(a,b){})(jQuery,window);//jQuery被优化为a,window被优化为b
3.全局变量this定义
var_oSelf=this, $this=$(this);
使得在插件的函数内可以使用指向插件的this
4.插件配置
this.oConfig=$.extend(defaults,_aoConfig);
设置默认参数,同时也可以再插件定义时传入参数覆盖默认值
5.初始化函数
一般的插件会有init初始化函数并在插件的尾部初始化
6.私有函数、公有函数
私有函数:插件内使用,函数名使用”_”作为前缀标识
共有函数:可在插件外使用,函数名使用”this.”作为前缀标识,作为插件的一个方法供外部使用
7.returnthis
最后返回jQuery对象,便于jQuery的链式操作
写法二
主体结构
(function($){
$.fn.addrInput=function(_aoOptions){
var_oSelf=this;
_oSelf.sVersion='version-1.0.0';
_oSelf.oConfig={
nInputLimitNum:9
};
//插件配置
$.extend(_oSelf.oConfig,_aoOptions);
//调用这个对象的方法,传递this
$.fn.addrInput._initUI.call(_oSelf,event);
$.fn.addrInput._initEvents.call(_oSelf);
//提供外部函数
this.close=function(){
_close();
}
//返回jQuery对象,便于Jquery的链式操作
return_oSelf;
}
$.fn.addrInput._initUI=function(event){
var_oSelf=this,
_oTarget=$(event.currentTarget);
}
$.fn.addrInput._initEvents=function(){}
})(window.jQuery);
点评
1.美观
插件的方法写在外部,并通过在插件主体传递this的方式调用
2.定义插件版本号
不过在这里还是没有用到
3.关于call
这里的第一个参数为传递this,后面的均为参数
语法:
call([thisObj[,arg1[,arg2[,[,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。如果没有提供thisObj参数,那么Global对象被用作thisObj。
4.关于”this”
在插件的方法中,可能有用到指向插件的this、和指向事件触发的this,所以事件触发的this用event来获取:event.cuerrntTarget
event.currentTarget:指向事件所绑定的元素,按照事件冒泡的方式,向上找到元素
event.target:始终指向事件发生时的元素
如:
html代码
<divid="wrapper"> <ahref="#"id="inner">clickhere!</a> </div>
js代码
$('#wrapper').click(function(e){
console.log('#wrapper');
console.log(e.currentTarget);
console.log(e.target);
});
$('#inner').click(function(e){
console.log('#inner');
console.log(e.currentTarget);
console.log(e.target);
});
结果输出
#inner <ahref="#"id="inner">clickhere!</a> <ahref="#"id="inner">clickhere!</a> #wrapper <divid="wrapper"><ahref="#"id="inner">clickhere!</a></div> <ahref="#"id="inner">clickhere!</a>
写法三(原生写法)
主体结构
varMemberCard=function(_aoOption){
//配置(默认是从外面传进来)
_aoOption||(_aoOption={});
//初始化函数
_init(this);
}
var_init=function(_aoSelf){
//函数执行
_initData(_aoSelf);
//调用对象的私有方法
_aoSelf._timedHide();
}
var_initData=function(_aoSelf){}
//私有方法
MemberCard.prototype._timedHide=function(_aoOptions){
var_oSelf=this;
clearTimeout(this.iHideTimer);
//使用underscore.js的extend方法来实现属性覆盖
varoDefault=extend({nHideTime:300},_aoOptions);
_oSelf.iHideTimer=setTimeout(function(){
//调用对象的共有方法
_oSelf.hide();
},oDefault.nHideTime);
}
//公有方法
MemberCard.prototype.hide=function(_aoOptions){}
使用
varoColleagueCard=newMemberCard({nHideTime:200});
oColleagueCard.hide();
点评:
1.关于属性覆盖(对象深拷贝)
原生函数实现方法
functiongetType(o){
return((_t=typeof(o))=="object"?o==null&&"null"||Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase();
}
functionextend(destination,source){
for(varpinsource){
if(getType(source[p])=="array"||getType(source[p])=="object"){
destination[p]=getType(source[p])=="array"?[]:{};
arguments.callee(destination[p],source[p]);
}else{
destination[p]=source[p];
}
}
}
demo:
vartest={a:"ss",b:[1,2,3],c:{d:"css",e:"cdd"}};
vartest1={};
extend(test1,test);
test1.b[0]="change";//改变test1的b属性对象的第0个数组元素
alert(test.b[0]);//不影响test,返回1
alert(test1.b[0]);//返回change
基于jQuery的实现方法:
jQuery.extend([deep],target,object1,[objectN]);
用一个或多个其他对象来扩展一个对象,返回被扩展的对象。
如果不指定target,则给jQuery命名空间本身进行扩展。这有助于插件作者为jQuery增加新方法。如果第一个参数设置为true,则jQuery返回一个深层次的副本,递归地复制找到的任何对象。否则的话,副本会与原对象共享结构。未定义的属性将不会被复制,然而从对象的原型继承的属性将会被复制。
demo:
varoptions={id:"nav",class:"header"}
varconfig=$.extend({id:"navi"},options);//config={id:"nav",class:"header"}
2.关于this
这个对象的所有方法的this都指向这个对象,所以就不需要重新指定
写法四
主体结构
functionEditorUtil(){
this._editorContent=$('#editor_content');
this._picBtn=$('#project_pic');
this.ieBookmark=null;
}
EditorUtil.prototype={
consturctor:EditorUtil,
noteBookmark:function(){
},
htmlReplace:function(text){
if(typeoftext==='string'){
returntext.replace(/[<>"&]/g,function(match,pos,originalText){
switch(match){
case'<':
return'<';
case'>':
return'>';
case'&':
return'&';
case'"':
return'"';
}
});
}
return'';
},
init:function(){
this._memBtn.bind('click',function(event){
$(".error_content").hide();
returnfalse;
});
}
};
//初始化富文本编辑器
vareditor=newEditorUtil();
editor.init();
总结
写法四和写法三其实都差不多,但是你们有没有看出其中的不一样呢?
两种都是利用原型链给对象添加方法:
写法三:
MemberCard.prototype._timedHide MemberCard.prototype.hide
写法四:
EditorUtil.prototype={
consturctor:EditorUtil,
noteBookmark:function(){},
htmlReplace:function(){}
}
细看写法四利用“对象直接量”的写法给EditorUtil对象添加方法,和写法三的区别在于写法四这样写会造成consturctor属性的改变
constructor属性:始终指向创建当前对象的构造函数
每个函数都有一个默认的属性prototype,而这个prototype的constructor默认指向这个函数。如下例所示:
functionPerson(name){
this.name=name;
};
Person.prototype.getName=function(){
returnthis.name;
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Person);//true
console.log(Person.prototype.constructor===Person);//true
//将上两行代码合并就得到如下结果
console.log(p.constructor.prototype.constructor===Person);//true
functionPerson(name){
this.name=name;
};
Person.prototype.getName=function(){
returnthis.name;
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Person);//true
console.log(Person.prototype.constructor===Person);//true
//将上两行代码合并就得到如下结果
console.log(p.constructor.prototype.constructor===Person);//true
当时当我们重新定义函数的prototype时(注意:和上例的区别,这里不是修改而是覆盖),constructor属性的行为就有点奇怪了,如下示例:
functionPerson(name){
this.name=name;
};
Person.prototype={
getName:function(){
returnthis.name;
}
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Person);//false
console.log(Person.prototype.constructor===Person);//false
console.log(p.constructor.prototype.constructor===Person);//false
为什么呢?
原来是因为覆盖Person.prototype时,等价于进行如下代码操作:
Person.prototype=newObject({
getName:function(){
returnthis.name;
}
});
Person.prototype=newObject({
getName:function(){
returnthis.name;
}
});
而constructor属性始终指向创建自身的构造函数,所以此时Person.prototype.constructor===Object,即是:
functionPerson(name){
this.name=name;
};
Person.prototype={
getName:function(){
returnthis.name;
}
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Object);//true
console.log(Person.prototype.constructor===Object);//true
console.log(p.constructor.prototype.constructor===Object);//true
functionPerson(name){
this.name=name;
};
Person.prototype={
getName:function(){
returnthis.name;
}
};
varp=newPerson("ZhangSan");
console.log(p.constructor===Object);//true
console.log(Person.prototype.constructor===Object);//true
console.log(p.constructor.prototype.constructor===Object);//true
怎么修正这种问题呢?方法也很简单,重新覆盖Person.prototype.constructor即可:
functionPerson(name){
this.name=name;
};
Person.prototype=newObject({
getName:function(){
returnthis.name;
}
});
Person.prototype.constructor=Person;
varp=newPerson("ZhangSan");
console.log(p.constructor===Person);//true
console.log(Person.prototype.constructor===Person);//true
console.log(p.constructor.prototype.constructor===Person);//true
functionPerson(name){
this.name=name;
};
Person.prototype=newObject({
getName:function(){
returnthis.name;
}
});
Person.prototype.constructor=Person;
varp=newPerson("ZhangSan");
console.log(p.constructor===Person);//true
console.log(Person.prototype.constructor===Person);//true
console.log(p.constructor.prototype.constructor===Person);//true