0.引子
jQuery是一个极其优秀和成功的框架,不仅本身使用极其简单,还提供了良好插件机制。本文将向您展示开发jQuery插件是件很简单的事,还会阐述模块化思想独立于任何语言之上。
1. 授人与渔,先授人与鱼
告诉你怎么开发jQuery插件不仅是本文的目的,让您明白背后的思想才是本文的价值所在。刚接触jQuery时,那种引入插件文件就拥有插件功能的机制,曾让我欣奇不已。现在我将它带给你,开始旅程吧。
2. 这是一个没有尽头的例子。
我们要开发一个插件,或者说我们要开发一个模块。什么是模块,不解释,跟着本文慢慢理解吧。
文件命名格式:jquery.模块名.js。
a. 告诉我这段文字反过来怎么读?
这是我要给你举的例子,一段文字‘’反转’的例子。清楚了没,我们需要开发一个插件,这个插件具有文字’反转’功能,我将称它是’文本’模块。为什么呢?先告诉你,我可能还要让这段文字加点颜色。所以它是‘’文本'模块。一般而言,模块有多个功能。
第1步:包含jquery文件并创建一个js文件 jquery.text.js。
文件命名格式:jquery.模块名.js。
a. 告诉我这段文字反过来怎么读?
这是我要给你举的例子,一段文字‘’反转’的例子。清楚了没,我们需要开发一个插件,这个插件具有文字’反转’功能,我将称它是’文本’模块。为什么呢?先告诉你,我可能还要让这段文字加点颜色。所以它是‘’文本'模块。一般而言,模块有多个功能。
第1步:包含jquery文件并创建一个js文件 jquery.text.js。
<script type="text/javascript" src="/js/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="/js/jquery.text.js"></script>
第2步:编写jquery.text.js文件。
当您引入jquery文件时,已经有个对象供您使用了,jQuery,整个插件将围绕这个对象变量玩转。先看下匿名函数。
(function(){}); // 最简单的匿名函数声明
(function(){})(); // 最简单的匿名函数调用
(function($){})(jQuery); // 最简单插件实现
明白了没,为什么很多插件里,可以直接操作 $。用匿名函数主要是为了避免命名冲突,知道这点就行了。在实现之前,我们先看下如何调用。
jQuery(function(){
var s = $.text.reverse("请让我反过来");
console.log(s); // 输出 来过反我让请
});
直接给答案。
(function($){
$.extend($.text, {
reverse: function(str) {
var rv = '';
for (var i = str.length-1; i >= 0; i--) {
rv += str.substr(i, 1);
}
return rv;
}
});
})(jQuery);
分析下:$.extend是什么?$.extend(对象,{ ... }),就将 { ... } 自动加到对象身上,即它就是扩展的用处。$是个对象,对象可以任意加属性,比如这里 $.text,至于为什么这样处理呢呢?很简单,体现模块。比如 $.text 体现文本模块,$.validate 体现验证模块,$.slide 体现轮播模块,等等。 到目前为止,你是不是很迷惑,我真正想表达什么,我已经向您展示模块化了。其实jQuery的插件是最好的理解模块化的方式了。可能你是个面向对象高手,你明白类,对象,继承,多态,抽象,设计模式这些东东,但是还有些你不知道的。
有没有觉得 $.text 很像类,reverse是它的一个静态方法?继续看吧。
b. 嘿,需求变了:一段文字,点击它时,让它文字反过来。
跟前面相似,在实现之前,我们先看下如何调用。
<div id='demo'>原来我是这样子的</div>
jQuery(function(){
$('#demo').reverse();
});
我喜欢先上答案。
(function($){
$.extend($.fn, {
reverse: function() {
$(this).click(function() {
var newText = $.text.reverse($(this).text());
$(this).text(newText);
});
return this; // 这个返回是这样写吗?这是对的吗?是为了chain吗?
}
});
$.extend($.text, {
reverse: function(str) {
var rv = '';
for (var i = str.length-1; i >= 0; i--) {
rv += str.substr(i, 1);
}
return rv;
}
});
})(jQuery);
分析下:$.extend已经在上面说明过了。$.fn是什么呢?它表示任何jQuery元素对象,比如例子中的 $('#demo'),所以 $('#demo')这个对象扩展了一个方法 reverse,看它的实现细节,不难知道:点击时,将本身的文本反转。$.text.reverse上面说明过了。
3. 我准备制造麻烦了
我很无聊,经常会有一些可能别人认为没事找事做的想法。我现在有这样的需求:我不想点击时让它反转,我想初始化完就让它反转。聪明的你可能已经有方法应付了。
jQuery(function(){
$('#demo').reverse();
$('#demo').click(); // 你真是人才,是我出的太蠢了?
});
c. 嘿,需求又变了:一段文字,点击它时,先弹出框(我准备反转了),然后让它文字反过来。
调用如下:
jQuery(function(){
$('#demo').reverse();
});
我太喜欢先上答案了。
(function($){
$.extend($.fn, {
reverse: function() {
$(this).click(function() {
alert("我准备反转了"); // 加了一行弹出框,so easy
var newText = $.text.reverse($(this).text());
$(this).text(newText);
});
return this; // 呆会你就知道,专家说的话不一定是对的。
}
});
$.extend($.text, {
reverse: function(str) {
var rv = '';
for (var i = str.length-1; i >= 0; i--) {
rv += str.substr(i, 1);
}
return rv;
}
});
})(jQuery);
这段不分析了,直接继续变化。
d. 该死的需求又变了:在上面的基础上,我想初始化完,文字就反转。
怎么调用呢?
jQuery(function(){
$('#demo').reverse();
/* 不能click了,因为一点击,就弹出框了,怎么办呢?
$('#demo').click();
*/
// 好像可以这样,你觉得呢?你真的想把上面的实现代码copy过来吗?不是说好要重用的吗,哈哈。
var newText = $.text.reverse($('#demo').text());
$('#demo').text(newText);
});
不上答案,还真难怎么解释。继续上答案。
function($){
$.extend($.fn, {
reverse: function() {
var text = new $.text(this[0]); // 不用担心this[0],当作调用它的元素就行,后面会解释滴。
return text; // a. 返回一个对象了,不是this了,神奇。
}
});
// b. 构造出一个对象。
$.text = function(div) {
this.currentDiv = div;
this.init();
};
$.extend($.text, {
// c. 核心在这里。有点要说明的是,以下函数的 this 是指 text对象,已经不是那个操作元素了,它已经变成 this.currentDiv。
prototype: {
init: function() {
(this.currentDiv).click(function() {
alert("我准备反转了");
this.reverse();
});
},
reverse: function() {
var newText = $.text.reverse($(this.currentDiv).text());
$(this.currentDiv).text(newText);
}
},
reverse: function(str) {
var rv = '';
for (var i = str.length-1; i >= 0; i--) {
rv += str.substr(i, 1);
}
return rv;
}
});
})(jQuery);
怎么调用呢?
jQuery(function(){
var text = $('#demo').reverse();
text.reverse();
});
如果代码看不懂,请先看
打通JS任督二脉。
4. 为什么这样设计?模块化是也
兑现开篇的承诺,这篇文章不仅让您知道怎么开发JQ插件,也会让您明白背后的思想。
摆在你面前的是一个项目,是一个系统工程,在您清楚需求之后,开始实现时(包括设计,编码...),您怎么入手呢?
模块化,不是类,也不是对象。怎么说呢?以这个例子说明。
需求:请开发一个插件(模块),提供服务、接口、API(都是同一个概念),具有反转的功能(可能还有其它功能)。
所以本身应该先立足于模块,也许你还做些初始化呢,模块定义好,比如是 $.text,这有个好处,概念清楚,命名空间也出来了。 其它语言的命名空间,包,这些其实也是想划分好模块而已。就像一个组件,已经摆在你面前了。
然后,这个模块要提供服务,或工具函数,比如例子中的 $.text.reverse(str)。这很有意思了,静态类方法,普通函数,其实都是工具而已。 模块也提供对象构造能力,比如 var text = new $.text(ele); 这是很重要的一个面向对象思想。对象的初始化由谁提供呢?我认为是模块。 很多人编程时,忽略了这一点,因为构造对象一般只有一条语句,比如 xxx = new Xxx(...); 其实不然的,也有很多有复杂构造的。像nginx里的 ngx_log_init,有兴趣的人可以看下,它放在 ngx_log.c 文件里。 所以是先有模块,再有对象的,对象的结构定义在模块文件里,模块也是个对象,是具有属性的,本例比较简单,没有涉及到这点。
摆在你面前的是一个项目,是一个系统工程,在您清楚需求之后,开始实现时(包括设计,编码...),您怎么入手呢?
模块化,不是类,也不是对象。怎么说呢?以这个例子说明。
需求:请开发一个插件(模块),提供服务、接口、API(都是同一个概念),具有反转的功能(可能还有其它功能)。
所以本身应该先立足于模块,也许你还做些初始化呢,模块定义好,比如是 $.text,这有个好处,概念清楚,命名空间也出来了。 其它语言的命名空间,包,这些其实也是想划分好模块而已。就像一个组件,已经摆在你面前了。
然后,这个模块要提供服务,或工具函数,比如例子中的 $.text.reverse(str)。这很有意思了,静态类方法,普通函数,其实都是工具而已。 模块也提供对象构造能力,比如 var text = new $.text(ele); 这是很重要的一个面向对象思想。对象的初始化由谁提供呢?我认为是模块。 很多人编程时,忽略了这一点,因为构造对象一般只有一条语句,比如 xxx = new Xxx(...); 其实不然的,也有很多有复杂构造的。像nginx里的 ngx_log_init,有兴趣的人可以看下,它放在 ngx_log.c 文件里。 所以是先有模块,再有对象的,对象的结构定义在模块文件里,模块也是个对象,是具有属性的,本例比较简单,没有涉及到这点。
5. 我隐藏了些什么:选项
写一些原理性的文章时,我经常会有意忽略一些细节,我认为读者明白原理之后,有些细节的应该自己去体会,这是一个很好的提升空间,我不能剥夺。 只是要插件,基本会提供选项参数,是的。这篇文章没有提到这点。大概给个答案:
调用方式
调用方式
jQuery(function(){
var text = $('#demo').reverse({ opt1: v1, opt2: v2, ...});
text.reverse();
});
插件实现
(function($){
$.extend($.fn, {
reverse: function(options) {
var text = new $.text(options, this[0]); // 不用担心this[0],当作调用它的元素就行,后面会解释滴。
return text; // a. 返回一个对象了,不是this了,神奇。
}
});
// b. 构造出一个对象。
$.text = function(options, div) {
this.settings = $.extend(true, { }, $.text.defaults, options);
this.currentDiv = div;
this.init();
};
$.extend($.text, {
defaults: {
opt1: v1,
play: v2,
...
},
/* c. 核心在这里。有点要说明的是,以下函数的 this 是指 text对象,已经不是那个操作元素了,它已经变成 this.currentDiv。
* 以下函数可以使用选项:this.settings.opt1, ...
**/
prototype: {
init: function() {
(this.currentDiv).click(function() {
alert("我准备反转了");
this.reverse();
});
},
reverse: function() {
var newText = $.text.reverse($(this.currentDiv).text());
$(this.currentDiv).text(newText);
}
},
reverse: function(str) {
var rv = '';
for (var i = str.length-1; i >= 0; i--) {
rv += str.substr(i, 1);
}
return rv;
}
});
})(jQuery);
5. 进一步学习
通用jQuery实现的轮播插件,已经开源,有兴趣的可以看下源码。
验证插件,作者是jQuery的核心成员,代码很有学习价值。
补充:jQuery插件没有通用的开发模式,你要根据实际情况灵活变通,比如 return 对象 好呢,还是 return this 呢?看具体情况。
模块命名以名词为主,功能以动词为主,比如验证插件里的,调用时 $('#testform').validate(...); 返回一个 validator 对象。 validate vs validator。
验证插件,作者是jQuery的核心成员,代码很有学习价值。
补充:jQuery插件没有通用的开发模式,你要根据实际情况灵活变通,比如 return 对象 好呢,还是 return this 呢?看具体情况。
模块命名以名词为主,功能以动词为主,比如验证插件里的,调用时 $('#testform').validate(...); 返回一个 validator 对象。 validate vs validator。