(翻译)编写属于你的jQuery插件
Writing Your Own jQuery Plugins
原文地址:http://blog.teamtreehouse.com/writing-your-own-jquery-plugins
jQuery是一个优秀的javascript库。它不但拥有良好的跨浏览器兼容性,容易学习掌握,还可以轻而易举的为你的网站添加有趣的交互。同时,大量的jQuery插件能够让你随心所欲的完成你想要完成的工作。
然而在平常的工作中,并不是总能够找到可以完全满足需求的插件,或者你想要将一些常用的功能封装以便保证项目的可重用性。在这些情形下,编写自己的jQuery插件是一个很好的满足需求的手段。
其实,编写jQuery插件并没有想像中难。这篇文章,我们将通过编写一个简单的插件,为其添加一些参数和事件回调,来向你完整的展示整个流程。
准备工作
像大多数编程指南的开篇一样,我们也以一个“Hello World”插件开始吧。在开始之前,先创建必要的文件并在HTML文档中引用它们。首先,在站点的js目录下新建插件文件jquery.hello-world.js。通常,jQuery插件的命名以“jquery.”开始。
接下来,需要在HTML文件中链接插件文件及其依赖的jquery核心库。我们将以下两行放置在文档的底部,也即</body>标签之前。
<script src="js/jquery-1.9.1.min.js"></script> <script src="js/jquery.hello-world.js"></script>
jQuery插件的结构
jQuery本身已经为插件开发提供了足够的支持。但我们依然需要遵循JavaScript开发的最佳实践,将所有的代码都囊括在一个本地作用域中。下面是一个常规的jQuery插件基础结构:
(function($) { $.fn.helloWorld = function() { // Future home of "Hello, World!" } }(jQuery));
让我们花一点时间来理解一下上述代码的含义。通过将所有代码包含在 (function() {}) 自执行代码块中,能够保证插件内部的所有变量不会污染到全局变量。毕竟,我们肯定不愿意看到编写的插件和页面中的其它JavaScript代码发生冲突。
可能你已经注意到,我们定义插件的方式,就好像jQuery也位于其“无冲突”模式。再一次强调,我们需要避免插件与页面中其它JavaScript发生冲突,这也包括使用到的'$'符号。该符号很有可能被其它的JavaScript库使用。
最后,$.fn这种写法是jQuery定义插件的方式。这里将其命名为helloWorld并将所有代码放置在该函数中。下面,让我们做一些真正有意义的工作。
插件代码编写
为了使插件的代码尽可能的简单,同时又能达到演示的目的,我们将要完成的功能是改变关联的元素的文本内容为“hello world”。这已经近乎于傻瓜式的操作了。
(function($) { $.fn.helloWorld = function() { this.each( function() { $(this).text("Hello, World!"); }); } }(jQuery));
当在jQuery选择器上调用上述插件时,关联的元素已经是一个jQuery对象,因此不再需要再用"$(this)"结构进行包装。然而,如果需要在一个循环中使用每一个匹配的元素,比如$.each(),则一般会使用$(this)对循环的元素进行包装。
假如我们想要改变下面页面中所有的<h2>标题的文本:
调用的方式你已经很熟悉了,就像这样:
<script> $(document).ready( function() { $('h2').helloWorld(); }); </script>
结果为:
我们能做的远远不止如此。当上面的插件调用时,实际上完全被隔离在了它本身的作用域。也就是说,调用的作用链在插件内部被终止了。如果你试着以jQuery的链式写法调用其它操作时,是不会生效的。为了修复这个问题,需要确保返回每个DOM元素循环后的结果:
(function($) { $.fn.helloWorld = function() { return this.each( function() { $(this).text("Hello, World!"); }); } }(jQuery));
恭喜你,已经写出了你人生中的第一个jQuery插件!
稍安勿躁,好戏在后头!
现在你可能正沉浸在编写出自己的jQuery插件的喜悦中,这时候你的老大跑过来说,想要把现在的网站翻译成西班牙语。老天爷,现在该咋整?
其实,可以简单的添加一个参数完成上述需求。重新审视上述插件的代码,我们可以替换其内部的硬编码文本内容为一个变量,在调用的时候传入该变量的值。
(function($) { $.fn.helloWorld = function( customText ) { return this.each( function() { $(this).text( customText ); }); } }(jQuery));
如你所见,在调用的时候可以传入任何想要传入的文本值。办公室的马德里先生已经将我们需要的东西翻译成了西班牙语,现在可以使用参数的方式调用该插件:
<script> $(document).ready( function() { $('h2').helloWorld('¡Hola, mundo!'); }); </script>
页面呈现如下:
完全自由的控制
可能你已经感觉到一点担忧,如果在调用插件的时候没有传入文本怎么办?显而易见,页面中匹配的元素的文本会被插件修改为空值,这可能不是我们预期的结果。另外,我们的老大已经跑过来要求添加一个变量,如果他再跑过来要求添加更多的可定制化的选项怎么办?不断的添加参数毕竟不是长远之计,一劳永逸的解决方法是通过options对象。
我们已经知道具体的文本内容需要由外部传入,现在,老大又要求文本颜色和字体大小同样在调用时指定(好吧,我知道颜色和字体大小都可以通过jQuery内置的函数进行控制,这里仅仅是一个演示)。让我们为插件添加一个options参数对象,同时通过$.extend方法与默认的参数进行合并:
(function($) { $.fn.helloWorld = function( options ) { // Establish our default settings var settings = $.extend({ text : 'Hello, World!', color : null, fontStyle : null }, options); return this.each( function() { // We'll get back to this in a moment }); } }(jQuery));
现在我们有了可供外部设置的选项。如果调用的时候缺少了待替换的文本,将会使用指定的默认值。另外,在配置参数中添加了"color"和"fontStyle"两个属性,默认值均为"null"。如果在CSS文件中已经定义了颜色值和字体大小,在插件调用时不需要再重复指定。当然,二者均可以在插件内部重写。代码如下:
return this.each( function() { $(this).text( settings.text ); if ( settings.color ) { $(this).css( 'color', settings.color ); } if ( settings.fontStyle ) { $(this).css( 'font-style', settings.fontStyle ); } });
因为该插件的目的是替换文本。如果提供的文本值为空则没有任何意义,这也是在插件内部为其指定默认值的原因,当参数缺失时显示缺省值总比空白要好得多。另一方面,颜色值和字体风格则只需要提供一个可供重写的入口。
这时候老大跑过来说想要将站点翻译为法语,另外还希望将文本颜色改为蓝色,字体调整为italic。幸运的是,我们的插件处理这些需求毫无压力:
$('h2').helloWorld({ text : 'Salut, le monde!', color : '#005dff', fontStyle : 'italic' });
你看,我们的插件已经拥有了一些由外部控制的参数。将来可以在不影响现有调用的基础上,非常容易的添加或去掉某个参数。这些都益于良好的可扩展性。
但我们的老大带着另外一个需求再一次跑了过来:一旦插件按其预定的方式调用完毕,应该弹出一个对话框通知用户这个消息。为了达到这个目的,有两个选择:要么辞职,因为这个家伙明显无视最初的需求文档。要么不走这么极端的路线,为我们的插件添加处理回调函数的能力。
回调函数是JavaScript函数的参数,同时它本身也是一个JavaScript函数。其并不是jQuery独有的特性。初看上去回调函数是一个复杂的概念,但实际它的使用非常简单。
我们需要做的仅仅是在配置对象中附加一个额外的参数:
// Establish our default settings var settings = $.extend({ text : 'Hello, World!', color : null, fontStyle : null, complete : null }, options);
现在,当插件调用完毕时,就该附加的“complete”参数生效了。为了调用该参数,首先需要确保其是一个真正的函数。幸运的是,jQuery已经内置了$.isFunction函数帮助我们完成这个检测:
return this.each( function() { // Our plugin so far if ( $.isFunction( settings.complete ) ) { settings.complete.call( this ); } });
页面中调用如下:
$('h2').helloWorld({ text : 'Salut, le monde!', color : '#005dff', fontStyle : 'italic', complete : function() { alert( 'Done!' ) } });
最终的效果,插件执行完毕后会触发回调函数:
结语
当你一遍又一遍的写着大量重复的JavaScript代码,自定义jQuery插件是一种很好摆脱这种泥沼的方式。通过自定义插件,你在保持自己代码DRY(dont repeat yourself)的同时,也确保了全局名称空间的独立和纯度。
如果你想调试上述示例代码,可以在我的 GitHub 获取。