jQuery 的流行在很大程度上归功于其对插件的支持。插件也就是功能扩展的意思,jQuery 允许任何开发人员超越最初的库函数创建并扩展 jQuery 函数。这种开放性框架设计思路催生了无数实用型的插件,jQuery 几乎能够提供 Web 应用程序内所需的任何一种函数。
jQuery 的易扩展性吸引了越来越多的开发者和业余爱好者去研究、设计和使用 jQuery 插件。目前,全球有超过上千种不同应用需要的插件。使用这些插件能够帮助开发人员提升开发速度,节约劳动成本。最权威的插件可以访问 jQuery 官方网站 (http://plugins.jquery.com) 获取。本章将重点讲解如何创建自己的 jQuery 插件,并就网络上比较经典的几个插件设计原理和使用进行介绍。
8.1 创建 jQuery 插件
网络上流传着成百上千的第三方插件,这些插件虽然能够增强我们的编程体验,但是很难满足所有设计需要,特别是个性化开发需求。如果自己编写的代码可以重用,或者供其他用户参考,很自然任何人都希望把这些代码进行封装,打包为一个新插件。这个实现过程并不困难,只要读者认真阅读本节内容即可轻松实现。
8.1.1 jQuery 插件的类型
jQuery 插件主要分为三种类型,说明如下。
1. jQuery 方法
这种类型的插件是把一些常用或者重复使用的功能定义为函数,然后绑定到 jQuery 对象上,从而成为 jQuery 对象的一个扩展方法。
目前,大部分jQuery插件都是这种类型的插件,由于这种插件是将对象方法封装起来,在 jQuery 选择器获取 jQuery 对象的过程中进行操作,从而发挥 jQuery 强大的选择器优势。有很多 jQuery 内部方法,也是在 jQuery 脚本内部通过这种形式插入到 jQuery 框架中的,如 parent() 、appendTo() 和 addClass() 等方法。
2.全局函数
也可以把自定义的功能函数独立附加到 jQuery 命名空间下,从而作为 jQuery 作用域下的一个公共函数使用。例如,jQuery 的 ajax() 方法就是利用这种途径内部定义的全局函数。
==由于全局函数没有被绑定到 jQuery 对象上,故不能够在选择器获取的 jQuery 对象上调用。需要通过 jQuery.fn() 或者 $.fn() 方式进行引用。==
3.选择器
jQuery 提供了强大的选择器,当然在个性化开发中,读者也可能会感觉到这些选择器不够用,或者使用不是很方便。这个时候,我们就可以考虑自定义选择器,以满足特定环境下的选择元素需要。
8.1.2 解析 jQuery 插件机制
为了方便用户创建插件,jQuery 自定义了 jQuery.extend() 和 jQuery.fn.extend() 方法。其中 jQuery.extend() 方法能够创建全局函数或者选择器,而 jQuery.fn.extend() 方法能够创建 jQuery 对象方法。
例如,下面的示例将在 jQuery 命名空间上创建两个公共函数。
- <script type="text/javascript">
- jQuery.extend({ // 扩展 jQuery 的公共函数
- minValue: function(a, b){ // 比较两个参数值,返回最小值
- return a<b?a:b;
- },
- maxValue: function(a, b){ // 比较两个参数值,返回最大值
- return a<b?b:a;
- }
- });
- </script>
- <script type="text/javascript">
- jQuery.extend({ // 扩展 jQuery 的公共函数
- minValue: function(a, b){ // 比较两个参数值,返回最小值
- return a<b?a:b;
- },
- maxValue: function(a, b){ // 比较两个参数值,返回最大值
- return a<b?b:a;
- }
- });
- $(function(){
- $("input").click(function(){
- var a = prompt("请输入一个数值?");
- var b = prompt("请再输入一个数值?");
- var c = jQuery.minValue(a, b);
- var d = jQuery.maxValue(a, b);
- alert("你输入的最大值是:" + d + "\n你输入的最小值是:" + c);
- });
- });
- </script>
- </head>
- <body>
- <input type="button" value="jQuery 插件扩展测试" />
- </body>
jQuery.extend() 和 jQuery.fn.extend() 方法都包含一个参数,该参数仅接受名/值对结构的对象,其中名表示函数或方法名,而值表示函数体。关于如何创建 jQuery 插件的问题,我们将在下面小节中进行详细讲解。
jQuery.extend() 方法除了可以创建插件外,还可以用来扩展 jQuery 对象。例如,在下面的示例中,调用 jQuery.extend() 方法把对象 a 和 b 合并为一个新的对象,并返回合并对象将其赋值给变量 c 。
- <script type="text/javascript">
- var a = { // 对象直接量
- name: "zhu",
- pass: 123
- };
- var b = { // 对象直接量
- name: "wang",
- pass: 456,
- age: 1
- };
- var c = jQuery.extend(a, b); // 合并对象a 和 b
- $(function(){
- for(var name in c){ // 遍历对象c,显示合并后的对象c的具体属性和值
- $("div").html($("div").html() + "<br/>" + name + ":" + c[name]);
- }
- });
- </script>
- </head>
- <body>
- <div></div>
- </body>
在实际开发中,常常使用 jQuery.extend() 方法为插件方法传递系列选项结构的参数。
这样当在调用该方法时,如果想传递新的参数值,就会覆盖掉默认的参数选项值,或者向函数参数添加新的属性和值;如果没有传递参数,则保持并使用默认值。例如,在下面几个函数调用中,分别传入新值,或者添加新参数,或者保持默认值。
- <script type="text/javascript">
- function fn(options){
- var options = jQuery.extend({ // 默认参数选项列表
- name1: value1,
- name2: value2,
- name3: value3,
- }, options); // 使用函数的参数覆盖或合并到默认参数选项列表中
- // 函数体
- }
- fn({name1: value2,name2: value3, name3: value1}); // 覆盖新值
- fn({name4: value4,name5: value5}); // 添加新选项
- fn(); // 保持默认参数值
- </script>
jQuery.extend() 方法的对象合并机制,比传统的逐个检测参数不仅灵活且简洁,使用命名参数添加新选项也不会影响已编写的代码风格,让代码变得更加直观明白。
8.1.3 创建 jQuery 全局函数
jQuery 内置的很多方法都是通过全局函数实现的。所谓全局函数,就是 jQuery 对象的方法,实际上就是位于 jQuery 命名空间内部的函数。
有人把这类函数称为实用工具函数,这些函数有一个共同特征,就是不直接操作 DOM 元素,而是操作 JavaScript 的非元素对象,或者执行其他非对象的特定操作,如 jQuery 的 each() 函数和 noConflict() 函数。
回忆一下上一章讲解过的 ajax() 方法,它就是一个典型的 jQuery 全局函数,$.ajax() 所做的一切都可以通过调用名称为 ajax() 的全局函数来实现。但是,这种方式会带来函数冲突问题,如果把函数放置在 jQuery 命名空间内,就会降低这种冲突,只要在 jQuery 命名空间内注意不要与 jQuery 其他方法冲突即可。
在上一节中曾经介绍了使用 jQuery.extend() 方法可以扩展 jQuery 对象的全局函数。当然,我们也可以使用下面的方法快速定义 jQuery 全局函数。例如,针对上一节示例,我们也可以按如下方法进行编写。
- <script type="text/javascript">
- jQuery.minValue = function(a,b){
- return a<b?a:b;
- };
- jQuery.maxValue = function(a,b){
- return a<b?b:a;
- };
- </script>
考虑到 jQuery 的插件越来越多,因此在使用时可能会遇到自己的插件与第三方插件名发生冲突的问题。为了避免这个问题,建议把属于自己的插件都封装在一个对象中。例如,针对上面两个创建的全局函数,可以把它们封装在自己的对象中,实现代码如下。
- <script type="text/javascript">
- jQuery.css8 = {
- minValue: function(a,b){
- return a<b?a:b;
- },
- maxValue: function(a,b){
- return a<b?b:b;
- }
- };
- </script>
- var c = jQuery.css8.minValue(a,b);
- var d = jQuery.css8.maxValue(a,b);
8.1.4 使用 jQuery.fn 对象创建 jQuery 对象方法
除了全局函数外,jQuery 中的大多数功能都是通过 jQuery 对象的方法提供的,这些对象方法对于 DOM 操作来说非常方便,这也是很多用户喜欢并选用 jQuery 框架的原因。
创建全局函数只需要通过为 jQuery 对象添加属性即可,而创建 jQuery 对象方法也可以通过为 jQuery.fn 对象添加属性来实现。实际上 jQuery.fn 对象就是 jQuery.prototype 原型对象的别名,使用别名更方便引用。例如,下面这个函数就是一个简单的 jQuery 对象方法,当调用该方法时,将会弹出一个提示对话框。
- <script type="text/javascript">
- jQuery.fn.test = function(){
- alert('这是jQuery对象方法!');
- };
- </script>
- <script type="text/javascript">
- jQuery.fn.test = function(){
- alert('这是jQuery对象方法!');
- };
- $(function(){
- $("input").click(function(){ // 绑定 click 事件
- $(this).test(); // 在当前的 jQuery 对象上调用 test() 方法
- });
- });
- </script>
- </head>
- <body>
- <input type="button" value="jQuery插件扩展测试" />
- </body>
由于这里并没有在任何地方匹配 DOM 节点,所以编写全局函数也是可以的。但是,在使用 jQuery 对象方法时,函数体内的 this 关键字总是引用当前 jQuery 对象,因此我们可以对上面的方法进行重写,实现动态提示信息。代码如下。
- <script type="text/javascript">
- jQuery.fn.test = function(){
- alert(this[0].nodeName); // 提示当前 jQuery 对象的 DOM 节点名称
- };
- $(function(){
- $("input").click(function(){ // 绑定 click 事件
- $(this).test(); // 在当前的 jQuery 对象上调用 test() 方法
- });
- });
- </script>
- </head>
- <body>
- <input type="button" value="jQuery插件扩展测试" />
- </body>
这样,当单击按钮时,就会弹出当前元素的节点名称。
在上面的示例中,可以看到由于 jQuery 选择器返回的是一个数组类型的 DOM 节点集合,this 指针就指向当前这个集合,故要显示当前元素的节点名称,就必须在 this 指针后面指定当前元素的序号。
试想,如果 jQuery 选择器匹配多个元素,我们该如何准确指定当前元素对象呢?
要解决这个问题,我们不妨在当前 jQuery 对象方法的环境中调用 each() 方法,通过隐式迭代的方式,让 this 指针依次引用每一个匹配的 DOM 元素对象。这样也能够保持插件与 jQuery 内置方法保持一致。例如,针对上面的示例做进一步的修改。
- jQuery.fn.test = function(){
- this.each(function(){ // 遍历所有匹配的元素,此处的 this 表示对象集合,即 jQuery 对象
- alert(this.nodeName); // 显示当前元素的节点名称,此处的 this 表示元素对象
- });
- };
- <script type="text/javascript">
- jQuery.fn.test = function(){
- this.each(function(){ // 遍历所有匹配的元素,此处的 this 表示对象集合,即 jQuery 对象
- alert(this.nodeName); // 显示当前元素的节点名称,此处的 this 表示元素对象
- });
- };
- $(function(){
- $("body *").click(function(){ // 选择 body 元素下所有元素
- $(this).test(); // 为当前元素调用 test()方法,提示当前 DOM 元素对象的节点名称
- });
- });
- </script>
- </head>
- <body>
- <input type="button" value="jQuery插件扩展测试" />
- <div>div元素</div>
- <p>p元素</p>
- <span>span元素</span>
- </body>
用惯 jQuery 的用户可能习惯于连写行为,也就是说在调用一个方法之后,紧跟着调用另一个方法,如此连写不断,形成一个珍珠链,而且编写灵活、方便,也符号人的思维习惯。例如
$(this).test().hide().height();
要实现类似的行为连写功能,就应该在每个插件方法中返回一个 jQuery 对象,除非方法需要明确返回值。返回的 jQuery 对象通常就是 this 所引用的对象。如果使用 each() 方法迭代 this ,则可以直接返回迭代的结果。例如,针对上面的示例做如下进一步的修改。
- jQuery.fn.test = function(){
- return this.each(function(){ // 返回迭代的 jQuery 对象
- alert(this.nodeName);
- });
- };
- <script type="text/javascript">
- jQuery.fn.test = function(){
- return this.each(function(){ // 返回迭代的 jQuery 对象
- alert(this.nodeName);
- });
- };
- $(function(){
- $("body *").click(function(){ // 选择 body 元素下所有元素
- $(this).test().html(this.nodeName).hide(4000); // 行为连写
- });
- });
- </script>
- </head>
- <body>
- <input type="button" value="jQuery插件扩展测试" />
- <div>div元素</div>
- <p>p元素</p>
- <span>span元素</span>
- </body>
8.1.5 使用 extend() 方法创建 jQuery 对象方法
jQuery.extend() 方法能够创建全局函数,而 jQuery.fn.extend() 方法可以创建 jQuery 对象方法。如果读者明白了上一节使用 jQuery.fn 对象属性的方法创建 jQuery 对象方法,那么使用 extend() 方法创建 jQuery 对象就比较容易理解了。
例如,针对上节中介绍的示例,我们可以调用 jQuery.fn.extend() 方法来创建 jQuery 对象方法。具体代码如下。
- jQuery.fn.extend({
- test: function(){
- return this.each(function(){
- alert(this.nodeName);
- });
- }
- });
针对上面定义的 test() 方法,同样可以在 jQuery 选择器中直接调用。
- <script type="text/javascript">
- jQuery.fn.extend({
- test: function(){
- return this.each(function(){
- alert(this.nodeName);
- });
- }
- });
- $(function(){
- $("body *").click(function(){ // 选择 body 元素下所有元素
- $(this).test().html(this.nodeName).hide(4000); // 行为连写
- });
- });
- </script>
- </head>
- <body>
- <input type="button" value="jQuery插件扩展测试" />
- <div>div元素</div>
- <p>p元素</p>
- <span>span元素</span>
- </body>