当 jQuery 在2006年1月现身时,给我的第一印象,是这玩意儿构造得很精明。基于CSS选择器(CSS selectors)来打点一切,其思路相当灵巧(参考getElementsBySelector)。但链盒工事(chaining stuff)看起来更像个噱头,并且整体看来,jQuery库提供的功能并不能覆盖所有基础性的东西。因此我断定,jQuery只会昙花一现。
几个月以来,我逐渐明白自己想错了。从技术工艺上考量,jQuery十分凌厉。它用简洁的方法,把大量常用功能封装起来,并提供精巧的插入式API,来满足标准库之外的功能模块的实现。jQuery秉持的核心,乃DOM元素的集合(译注:通常是某些子集合)——它把元素集合作为一个根本,给高度抽象出来了。最重要的,是这种遵循最佳实践的抽象,能让jQuery与其他JavaScript代码相处融洽。
很多对jQuery的介绍,都是针对设计师和初级开发人员。接下来我想说明,为什么jQuery也会吸引那些富有经验的开发人员。
名称空间(Namespacing)
编写可重用的、优秀的JavaScript代码,其关键在于对名称空间的积极把控。JavaScript只拥有单一的、全局的名称空间(即window对象),而很多程序员(以及一些库)恣意地为之添加各种东西。要知道全局变量是魔鬼!聪明的开发人员,会使用类似组件模式的技术,来尽力减少全局对象的数量。
jQuery仅向全局名称空间引入一个标记:jQuery函数/对象。其余的要么是jQuery的直接属性(译注:原文‘directy property’系笔误,应是‘direct property’),要么就是调用jQuery函数所返回的对象的方法。
在语言升级方面,jQuery提供了很多函数(功能),但每个函数都被赋给jQuery对象的属性:jQuery.each,jQuery.extend,jQuery.grep,jQuery.map,jQuery.merge以及jQuery.trim。如此一来,它们就不会跟其他代码产生冲突。
声名狼藉的$函数(The infamous $ function)
刚才我说到,jQuery是唯一被引入的全局标记,其实并不尽然:$标记作为jQuery的快捷方式,也被引入进来。庆幸的是,$的存在不会带来负面影响:如果你需要让原始的$起死回生(比如,这之前你的代码使用了Prototype),你可以调用jQuery.noConflict()来恢复它。
如果你既想拥有$的便利,又不希望jQuery跟其他同样使用了全局$函数的代码发生冲突,可遵循jQuery文档所建议的惯用方式:
(function($) { // 在这个函数体里,$可作为jQuery的引用 // 很方便,对吧?})(jQuery);
把一切都附加到$标记的做法,曾让我认为jQuery华而不实。不过,从体系的角度来审视这种设计,一切又是非常明了的——尽管我常喜欢在代码中定义自己的$快捷方式。
选取元素(Selecting some elements)
jQuery的每个操作,都以选取DOM中一个或更多的节点(nodes)作为开始。jQuery(拥有一种真正的面向特定领域)的选取语法,是十分有趣的,它结合了CSS 1,CSS 2,部分CSS 3语法,一些XPath语法,以及一些特定的扩展。在这里我不会做详细介绍,我只列出几个有用的例子:
$('div.panel')
选取了所有class="panel"的div
$('p#intro')
选取了所有id="intro"的段落
$('div#content a:visible')
选取了id="content"的div中所有可见的链接
$('input[@name=email]')
选取了所有name="email"的输入域
$('table.orders tr:odd')
选取了类名为“orders”的表中所有的奇数行
$('a[@href^=")
选取了所有(以http://开头的)外部链接
$('p[a]')
选取了所有包含一个或多个链接的段落
上述例子中,:visible和:odd是jQuery实现的扩展,很具特色。而属性的选取使用@作为标记,其方式和XPath一样,要优于CSS 2。
jQuery的这套选取语法包罗万象,有些类似正则表达式,想完全消化是需要花上一段时间的。
通过jQuery的选取操作,我们能得到一些很棒的“素材”(beast)。它们是一个集合,包含了DOM元素,并且类似数组那样,拥有length属性;通过索引可以访问集合中的元素。在Firebug console的交互模式下,集合也被显示成一个数组,这个特性非常有用。集合实际上是一个jQuery对象,这个对象被赋予了很多方法(methods),用来查询,修改,扩展集合中的元素。
jQuery的方法(methods),本质上可分成三种:一种可以操作那些符合匹配的元素;一种可以返回第一个匹配到的对象的值;一种可以变更被选取的集合。
我不会列出所有的方法(可参考visualjquery.com),但我用例子做一下说明。如果你的浏览器装了Firebug,你可以以交互方式运行这些示例代码:首先使用这个bookmarklet(译注[1])把jQuery库载入至浏览器的任意页面,然后把示例代码粘贴到Firebug console中。
$('div#primary').width(300);
把id="primary"的div的宽度设为300px
$('p').css('line-height', '1.8em');
把所有段落的line-height设为1.8em
$('li:odd').css({color: 'white', backgroundColor: 'black'});
向间隔的list项添加两个CSS规则;注意css()函数可以用一个对象来代替两个字符串作为参数
$('a[@href^=", '_blank');
向所有(以http://开头的)外部链接添加“external”类,然后策略性地加上target="_blank"属性。这个示例用到了链盒(chaining),稍后会做介绍。
$('blockquote').each(function(el) { alert($(this).text()) });
遍历页面上的每个<blockquote>,并显示出它的文字内容(包括HTML标签)
$('a').html('Click here!');
用阴险的“Click here!”代替页面上所有的链接<a>的文字
下面的示例展示了jQuery如何取得第一个匹配到的对象的值:
var width = $('div').width();
页面上第一个div的宽度
var src = $('img').attr('src');
页面上第一张图片的src属性值
var color = $('h1').css('color');
第一个<h1>的颜色样式值
在jQuery 的方法构造中,蕴含着令人惬意的对称性:当向方法传递两个参数或一个对象时,方法可被用来执行设置操作;如果只向方法传递一个参数,则可以让它执行取值操作(译注:读者可对照上面的示例代码感受一下)。这种对称性设计贯穿了jQuery体系,使得API的文法更容易被记忆。
本节最后的例子,展示了一些可变更被选取的元素集合的方法。这些方法大多都提高了检索DOM的简易程度:
$('div').not('[@id]')
返回那些没有id属性的div
$('h2').parent()
返回那些是<h2>的直接父节点元素
$('blockquote').children()
返回所有<blockquote>的子节点元素
$('p').eq(4).next()
在页面上找到第五个段落(译注:因为集合的元素索引从0开始),然后根据节点的树层结构关系,找到并返回这个段落节点右侧的兄弟节点元素
$('input:text:first').parents('form')
找到并返回页面上第一个type="text"的输入域所在的form节点元素,parents()的可选参数是另一个选择器
链盒(Chaining)
jQuery 开发团队经常夸耀jQuery的链盒理念(译注[2]),甚至在网站首页上宣扬“jQuery将改变你编写JavaScript的方式”。我个人感觉,这么做多少有点误导大众,我愿意告诉大家,你完全可以取jQuery之长,却应避免冗长的方法链盒(chains of methods)。
也就是说,链盒有时会像变戏法一样。除了使用链盒将各种操作DOM的方法粘到一起,你也可以使用jQuery的end()方法,来实现在特定范围内推进或回溯你需要得到的元素。这个概念很难解释清楚。本质上讲,每次使用(诸如children()或filter())方法来改变元素集合时,你可以在这些方法之后使用end(),来重新定位你最初选取的元素集合。关于这点,Jesse Skinner在他的Simplify Ajax development with jQuery(译注[3])教程中给出了实例:
$('form#login') // 第一步,隐藏表单中那些带有'optional'类的<label> .find('label.optional').hide().end() // 第二步,为表单的密码输入域渲染上红色边框 .find('input:password').css('border', '1px solid red').end() // 第三步,为表单加上提交处理 .submit(function(){ return confirm('Are you sure you want to submit?'); });
这个示例读起来就像句俏皮话。整个过程是,先选取一个表单,再在其中选取一些元素做修改,然后回溯到表单,为它定义一个submit()处理。
示例很酷,但如果你不习惯,也可以不这么用。我就很乐意用自定义变量来规划代码。
操作DOM(DOM Manipulation)
jQuery提供了几个大规模操作DOM的卓越方法。第一种非常让人惊叹:jQuery()函数能把HTML片段插入DOM元素中(实际上,函数会留意以'<'打头的字符串参数):
var div = $('<div>Some text</div>');
一旦你创建好了div,便可以继续用链盒向其添加属性:
var div = $('<div>Some text</div>').addClass('inserted').attr('id', 'foo');
现在把div加到body上:
div.appendTo(document.body)
或用选择器把div加到已知元素的前面:
div.prependTo('div#primary')
处理事件(Handling events)
任何JavaScript库都需要事件处理能力,jQuery也不例外。类似attr()和css()的行为,各种与事件处理相关的方法也有双重用途:一种是把函数当作参数,赋给事件处理器;一种是不带参数,可以模拟事件被触发(译注:前提是事件已经定义,可参考visualjquery.com > Events > click()):
$('p').click(function() { $(this).css('background-color', 'red'); });
为所有段落增加点击事件,当你点击它们时,段落背景会变成红色
$('p:first').click()
然后在第一个段落上模拟点击的动作,它的背景会变成红色
类似的函数还包括mouseover,keyup等,对应着浏览器通常支持的那些动作。留意一下事件处理中的'this'关键字,它代表触发事件的元素;$(this)是一种惯用语法,可以让this所代表的元素应用各种jQuery方法。
这里有两个与事件相关的函数值得仔细说一下:
$('a').hover(function() { $(this).css('background-color', 'orange');}, function() { $(this).css('background-color', 'white');});
hover()可设定两个函数,分别对应onmouseover和onmouseout事件。
$('p').one('click', function() { alert($(this).html()); });
one()设定的事件在第一次被触发后便被移除。上面的示例会让所有段落在第一次被点击时显示其文字内容。
凭借bind()和trigger()方法,jQuery也可以支持自定义事件(click()家族仅仅是便捷方法,只支持有限的事件)。自定义事件可接受参数,trigger()可接受数组作为参数,来做各种处理操作:
$(document).bind('stuffHappened', function(event, msg) { alert('stuff happened: ' + msg);});$(document).trigger('stuffHappened', ['Hello!']);
渐进式编码(Unobtrusive scripting)
本小节的标题很令我钟意。我一直认为,最好的Web应用程序,往往是那些在脚本被禁用后仍能正常使用的程序。想建立这样的应用程序,最好的方法就是遵循渐进式编码,让普通页面完全加载后,再为页面中的元素赋以事件处理(更多信息可参考渐进式编码和Hijax)。
jQuery对这种编码策略提供了绝好支持。首先,从整体上看,节点选取暗合jQuery以及渐进式编码的核心理念。其次,针对window.onload问题,jQuery提供了一套解决方案,这套方案借鉴了Dean Edward的成果,使得以“DOM加载完毕”为信号的事件能跨浏览器工作。你可以在浏览器完全加载DOM后设定并运行一个函数,如下所示:
$(document).ready(function() { alert('The DOM is ready!');});
你甚至可以直接传递一个函数给$(),以更简洁的方式达到同样效果:
$(function() { alert('The DOM is ready!');});
jQuery与Ajax(jQuery and Ajax)
在我所知道的主流JavaScript库中,jQuery拥有最棒的Ajax API。最简单的Ajax调用如:
$('div#intro').load('/some/fragment.html');
代码以GET请求方式,从/some/fragment.html文件中获取HTML片段,并把片段装载到id="intro"的div中。
当我第一次看到这行代码时,几乎对它没什么印象。这看起来非常简洁,但如果你想用jQuery做些更复杂的事情,比如显示Ajax装载进度,该如何做呢? jQuery为你准备了一些可自定义的事件(ajaxStart,ajaxComplete,ajaxError等等),来实现你想要的代码。同时 jQuery也提供了广泛的底层API,来实现更复杂的Ajax交互:
jQuery.get('/some/script.php', {'name': 'Simon'}, function(data) { alert('The server said: ' + data);}); // 以GET方式通过/some/script.php?name=Simon获取数据jQuery.post('/some/script.php', {'name': 'Simon'}, function(data) { alert('The server said: ' + data);}); // 以POST方式向/some/script.php发送请求jQuery.getJSON('/some.json', function(json) { alert('JSON rocks: ' + json.foo + ' ' + json.bar);}); // 从/some.json接收并解析数据,把数据转换成JSON格式jQuery.getScript('/script.js'); // 以GET方式获取/script.js脚本并用eval()执行
插件(Plugins)
就你所能获得的功能的数量而言,jQuery库其实是相当小的——对代码做紧凑处理后只有20KB左右,甚至用gzip压缩后会变得更小。向标准库添加额外功能时,需用插件的方式来做,它可以(也确实能够)向现有的jQuery实例对象添加全新的方法。如果你想执行:
$('p').bounceAroundTheScreenAndTurnGreen();
jQuery的插件机制提供了文档说明型的挂载方式(documented hooks),可以实现把上述方法添加到jQuery中。这种简易的创建形式,吸引了很多插件作者,他们让人印象深刻;现在插件目录中已经有超过100个插件了。