第19章 jQuery类库
JavaScript的核心API设计得很简单,但由于浏览器之间的严重不兼容性,导致客户端的API过于复杂。IE9的到来,缓解了这种不兼容性导致的糟糕境况,然而使用JavaScript框架或工具类库,能简化通用操作,能隐藏浏览器之间的差异,这让很多程序员在开发Web应用时变得更简单。撰写本书时,最流行和广泛采用的类库之一就是jQuery[1]。
jQuery类库如此广泛地使用,作为Web开发者,我们必须熟悉它:即便没有在自己的代码中使用它,也很有可能在他人写的代码中遇见。幸运的是,jQuery足够小巧和稳定,本书就可以把它讲述清楚。本章将全面介绍jQuery,第四部分还包括jQuery的快速参考。在第四部分,jQuery方法没有设独立词条,但jQuery为每个方法都给出了概要说明。
jQuery能让你在文档中轻松找到关心的元素,并对这些元素进行操作:添加内容、编辑HTML属性和CSS属性、定义事件处理程序,以及执行动画。它还拥有Ajax工具来动态发起HTTP请求,以及一些通用的工具函数来操作对象和数组。
正如其名,jQuery类库聚焦于查询。一个典型查询使用CSS选择器来识别一组文档元素,并返回一个对象来表示这些元素。返回的对象提供了大量有用的方法来批量操作匹配的元素。这些方法会尽可能返回调用对象本身,这使得简洁的链式调用成为可能。jQuery如此强大和好用,关键得益于以下特性:
·丰富强大的语法(CSS选择器),用来查询文档元素
·高效的查询方法,用来找到与CSS选择器匹配的文档元素集
·一套有用的方法,用来操作选中的元素
·强大的函数式编程技巧,用来批量操作元素集,而不是每次只操作单个
·简洁的语言用法(链式调用),用来表示一系列顺序操作
本章首先会介绍如何使用jQuery来实现简单查询并操作其结果。接下来的章节会讲解:
·如何设置HTML属性、CSS样式和类、HTML表单的值和元素内容、位置高宽,以及数据
·如何改变文档结构:对元素进行插入、替换、包装和删除操作
·如何使用jQuery的跨浏览器事件模型
·如何用jQuery来实现动画视觉效果
·jQuery的Ajax工具,如何用脚本来发起HTTP请求
·jQuery的工具函数
·jQuery选择器的所有语法,以及如何使用jQuery的高级选择方法
·如何使用和编写插件来对jQuery进行扩展
·jQuery UI类库
19.1 jQuery基础
jQuery类库定义了一个全局函数:jQuery()。该函数使用频繁,因此在类库中还给它定义了一个快捷别名:$。这是jQuery在全局命名空间中定义的唯一两个变量[2]。
这个拥有两个名字的全局方法是jQuery的核心查询方法。例如,下面的代码能获取文档中的所有<div>元素:
var divs=$("div");
该方法返回的值表示零个或多个DOM元素,这就是jQuery对象。注意:jQuery()是工厂函数,不是构造函数,它返回一个新创建的对象,但并没有和new关键字一起使用。jQuery对象定义了很多方法,可以用来操作它们表示的这组元素,本章中的大部分文字将用来阐释这些方法。例如,下面这段代码可以用来找到所有拥有details类的p元素,将其高亮显示,并将其中隐藏的p元素快速显示出来:
$("p.details").css("background-color","yellow").show("fast");
上面的css()方法操作的jQuery对象是由$()返回的,css()方法返回的也是这个对象,因此可以继续调用show()方法,这就是链式调用,很简洁紧凑。在jQuery编程中,链式调用这个习惯用语很普遍。再举个例子,下面的代码可以找到文档中拥有"clicktohide"CSS类的所有元素,并给每一个元素都注册一个事件处理函数。当用户单击元素时,会调用事件处理程序,使得该元素缓慢向上收缩,最终消失:
$(".clicktohide").click(function(){$(this).slideUp("slow");});
获取jQuery
jQuery类库是免费软件,可以从http://jquery.com下载该软件。下载后,像下面这样通过<script>元素在Web页面中引入:
<script src="jquery-1.4.2.min.js"></script>
文件名中的"min"表示引入的是压缩版本的类库,已经去除不必要的注释和空格,变量名等内部标识符也替换成了更短的名字。
在Web应用中引入jQuery的另一个方式是使用内容分发网络,比如以下URL地址:
http://code.jquery.com/jquery-1.4.2.min.js
http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.min.js
http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js
本章讲述的是jQuery 1.4版本。如果使用的是其他版本,在必要时请替换上面URL中的版本号“1.4.2”[3]。如果使用Google CDN,可以用“1.4”来获取1.4.x系列中的最新版本,或者用“1”来获取版本号小于“2.0”的最新版本。通过上面这些众所周知的URL来加载jQuery的最大好处是,因为jQuery很流行,访问你的网站的用户,很有可能在访问其他网站时,已经下载过jQuery类库,保存在浏览器缓存中,无须重新下载了。
19.1.1 jQuery()函数
在jQuery类库中,最重要的方法是jQuery()方法(也就是$())。它的功能很强大,有4种不同的调用方式。
第一种也是最常用的调用方式是传递CSS选择器(字符串)给$()方法。当通过这种方式调用时,$()方法会返回当前文档中匹配该选择器的元素集。jQuery支持大部分CSS3选择器语法,还支持一些自己的扩展语法。19.8.1节将详细阐述jQuery选择器语法。还可以将一个元素或jQuery对象作为第二参数传递给$()方法,这时返回的是该特定元素或元素集的子元素中匹配选择器的部分。这第二参数是可选的,定义了元素查询的起始点,经常称为上下文(context)。
第二种调用方式是传递一个Element、Document或Window对象给$()方法。在这种情况下,$()方法只须简单地将该Element、Document或Window对象封装成jQuery对象并返回。这样可以使得能用jQuery方法来操作这些元素而不用使用原生DOM方法。例如,在jQuery程序中,经常可以看见$(document)或$(this)。jQuery对象可以表示文档中的多个元素,也可以传递一个元素数组给$()方法。在这种情况下,返回的jQuery对象表示该数组中的元素集。
第三种调用方式是传递HTML文本字符串给$()方法。在这种调用方式下,jQuery会根据传入的文本创建好HTML元素并封装为jQuery对象返回。jQuery不会将刚创建的元素自动插入文档中,可以使用19.3节描述的jQuery方法来轻松地将元素插入想要的地方。注意:在这种调用方式下,不可传入纯文本,因为jQuery会把纯文本当成CSS选择器来解析。当使用这种调用风格时,传递给$()方法的字符串必须至少包含一个带有尖角括号的HTML标签。
通过第三种方式调用时,$()接受可选的第二参数。可以传递Document对象来指定与所创建元素相关联的文档。(比如,当创建的元素需要插入iframe里时,需要显式指定该iframe的document对象。)第二参数还可以是object对象。此时,假设该对象的属性表示HTML属性的键/值对,这些属性将设置到所创建的对象上。当第二参数对象的属性名是"css"、"html"、"text"、"width"、"height"、"offset"、"val"或"data",或者属性名是jQuery事件处理程序注册方法名时,jQuery将调用新创建元素上的同名方法,并传入属性值。(css()、html()、text()等方法将在19.2节讲述,事件处理程序注册方法将在19.4节讲述。)例如:
var img=$("<img/>",//新建一个<img>元素
{src:url,//具有HTML属性
css:{borderWidth:5},//CSS样式
click:handleClick//和事件处理程序
});
最后,第4种调用方式是传入一个函数给$()方法。此时,当文档加载完毕且DOM可操作时,传入的函数将被调用。这是例13-5中onLoad()函数的jQuery版本。在jQuery程序中,在jQuery()里定义一个匿名函数非常常见:
jQuery(function(){//文档加载完毕时调用
//所有jQuery代码放在这里
});
有时还可以看见$(f)的老式和完整写法:$(document).ready(f)。
传给jQuery()的函数在被调用时,this指向document对象,唯一的参数指向jQuery函数。这意味着可以释放全局的$()函数,但在内部依旧可以延续该习惯:
jQuery.noConflict();//还原$()为初始值
jQuery(function($){//让$()成为jQuery对象的局部别名
//jQuery代码放在这里
});
通过$()注册的函数将在DOMContentLoaded事件触发时由jQurey触发。当浏览器不支持该事件时,会在load事件触发时由jQurey触发。这意味着文档已经解析完毕,但图片等外部资源有可能还未加载。如果在DOM准备就绪后再传递函数给$(),传递的函数会在$()返回之前立刻调用。
jQuery类库还使用jQuery()函数作为其命名空间,在下面定义了不少工具函数和属性。上面提到过的jQuery.noConflict()就是其中一个工具函数。还包括用于通用遍历的jQuery.each(),以及用来解析JSON文本的jQuery.parseJSON()。19.7节列举了这些通用工具函数,jQuery的其他函数在本章中都会提及。
jQuery术语
在本章中将会遇到一些重要的术语和短语,我们来看一下其定义:
“jQuery函数”
jQuery函数是jQuery或$()的值。该函数可以用来创建jQuery对象,用来注册DOM就绪时需要调用的处理程序,还用做jQuery命名空间。我通常用$()来引用它。它可以用做命名空间,因此jQuery函数也可称为“全局jQuery对象”,但要注意千万别把它与“jQuery对象”混淆。
“jQuery对象”
jQuery对象是由jQuery函数返回的对象。一个jQuery对象表示一组文档元素,也叫做“jQuery结果”、“jQuery集”或“包装集”。
“选中元素”
当传递CSS选择器给jQuery函数时,它返回的jQuery对象表示匹配该选择器的文档元素集。在描述jQuery对象的方法时,我经常会使用“选中元素”这个说法,用来指代这些匹配的元素。例如,为了解释attr()方法,我会用“attr()方法给选中元素设置HTML属性”,用来替代更精确但很冗长的描述:“attr()方法给调用它时所在的jQuery对象的元素设置HTML属性”。注意“选中”是指CSS选择器,与用户执行的操作没有任何关系。
“jQuery函数”
jQuery函数指定义在jQuery命名空间中的函数,比如jQuery.noConflict()。jQuery函数也可称为“静态方法”。
“jQuery方法”
jQuery方法是由jQuery函数返回的jQuery对象的方法。jQuery类库最重要的部分就是它定义的这些强大的方法。
jQuery函数和jQuery方法有时很难区分,因为有部分函数和方法的名称是一样的。注意下面这两行代码的差异:
//jQuery的each()函数用来
//对数组a中的每一个元素都调用一次函数f
$.each(a,f);//调用jQuery()函数获取表示文档中所有<a>元素的jQuery对象
//然后调用该jQurey对象的each()方法
//对选中的每一个元素调用一次函数f
$("a").each(f);
19.1.2 查询与查询结果
传递CSS选择器字符串给$(),它返回的jQuery对象表示匹配(或称为“选中”)的元素集。CSS选择器在15.2.5节介绍过,你可以温习下15.2.5节中的例子——那里的所有例子传递给$()时都可以正常工作。jQuery支持的具体选择器语法会在19.8.1节详述。本节不会聚焦于那些高级选择器的细节,而会首先来看看可以如何处理查询结果。
$()的返回值是一个jQuery对象。jQuery对象是类数组:它们拥有length属性和介于0~length-1之间的数值属性。(请查看7.11节获取类数组对象的更多信息。)这意味着可以用标准的数组标识方括号来访问jQuery对象的内容:
$("body").length//=>1:文档只有唯一一个body元素
$("body")[0]//等于document.body
如果不想把数组标识用在jQuery对象上,可以使用size()方法来替代length属性,用get()方法来替代方括号索引。可以使用toArray()方法来将jQuery对象转化为真实数组。除了length属性,jQuery对象还有三个挺有趣的属性。selector属性是创建jQuery对象时的选择器字符串(如果有的话)。context属性是上下文对象,是传递给$()方法的第二参数,如果没有传递的话,默认是Document对象。最后,所有jQuery对象都有一个名为jquery的属性,检测该属性是否存在可以简单快捷地将jQuery对象与其他类数组对象区分开来。jquery属性值是字符串形式的jQuery版本号:
//获取document body中的所有<script>元素
var bodyscripts=$("script",document.body);
bodyscripts.selector//=>"script"
bodyscripts.context//=>document.body
bodyscripts.jquery//=>"1.4.2"
$()与querySelectorAll()
$()函数与15.2.5节中描述的Document对象的querySelectorAll()方法类似:两者都用CSS选择器作为参数,并且返回类数组对象来存放匹配选择器的的元素。在支持querySelectorAll()的浏览器中,jQuery实现会调用querySelectorAll()方法,然而,在代码中使用$()代替querySelectorAll()依旧是很好的选择:
·querySelectorAll()在新近的浏览器中才实现。$()在新、老浏览器中都能工作。
·jQuery可以通过手动实现选择,因此$()支持的CSS3选择器可以用在所有浏览器中,而不仅是那些支持CSS3的浏览器。
·$()返回的类数组对象(jQuery对象)比querySelectorAll()返回的类数组对象(NodeList)更加有用。
想要遍历jQuery对象中的所有元素时,可以调用each()方法来代替for循环。each()方法有点类似ECMAScript 5(ES5)中的forEach()数组方法。它接受一个回调函数作为唯一参数,然后它对jQuery对象中的每一个元素(按照在文档中的顺序)调用回调函数。回调函数作为匹配元素的方法来调用,因此在回调函数里this关键字指代Element对象。each()方法还会将索引值和该元素作为第一个和第二个参数传递给回调函数。注意:this和第二参数都是原生文档元素,而不是jQuery对象;如果想使用jQuery方法来操作该元素,需要先用$()封装它。
jQuery的e ach()方法和forEach()有一个显著区别:如果回调函数在任一个元素上返回false,遍历将在该元素后中止(这就像在普通循环中使用break关键字一样)。each()返回调用自身的jQuery对象,因此它可以用于链式调用。下面是个例子(使用的prepend()方法将在19.3节阐述):
//给文档中的div元素标号,从开始一直到div#last(包含边界值)
$("div").each(function(idx){//找到所有div元素,然后遍历它们
$(this).prepend(idx+":");//在每一个元素前面插入索引值
if(this.id==="last")return false;//碰到#last元素时终止
});
尽管each()方法很强大,但用得并不多,因为jQuery方法通常隐式遍历匹配的元素集并操作它们。需要使用each()的典型场景是需要用不同的方式来操作匹配的元素。即便如此,也不总需要调用each(),因为jQuery的一些方法允许传递回调函数。
在ES5数组方法规范化前,jQuery类库就已经存在了。jQuery定义了几个方法,其功能和ES5方法的功能类似。jQuery的map()方法和Array.prototype.map()方法很相近。它接受回调函数作为参数,并为jQuery对象中的每一个元素都调用回调函数,同时将回调函数的返回值收集起来,并将这些返回值封装成一个新的jQuery对象返回。map()调用回调函数的方式和each()方法相同:元素作为this值和第二参数传入,元素的索引值作为第一参数传入。如果回调函数返回null或undefined,该值将被忽略,在本次回调中不会有任何新元素添加到新的jQuery对象中。如果回调函数返回数组或类数组对象(比如jQuery对象),将会扁平化它并将其中的元素一个个添加到新的jQuery对象中。注意:由map()返回的jQuery对象可以不包含文档元素,但它依旧可以像类数组对象一样使用。例如:
//找到所有标题元素,映射到它们的id,并转化为真实数组,然后排序
$(":header").map(function(){return this.id;}).toArray().sort();
除了each()和map()之外,jQuery的另一个基础方法是index()。该方法接受一个元素作为参数,返回值是该元素在此jQuery对象中的索引值,如果找不到的话,则返回-1。显然,受jQuery的典型风格影响,index()方法有多个重载版本。如果传递一个jQuery对象作为参数,index()方法会对该对象的第一个元素进行搜索。如果传入的是字符串,index()会把它当成CSS选择器,并返回该jQuery对象中匹配该选择器的一组元素中第一个元素的索引值。如果什么参数都不传入,index()方法返回该jQuery对象中第一个毗邻元素的索引值。
这里要讨论的最后一个通用的jQuery方法是is()。它接受一个选择器作为参数,如果选中元素中至少有一个匹配该选择器时,则返回true。可以在each()回调函数中使用它,例如:
$("div").each(function(){//对于每一个<div>元素
if($(this).is(":hidden"))return;//跳过隐藏元素
//对可见元素做点什么
});
19.2 jQuery的getter和setter
jQuery对象上最简单、最常见的操作是获取(get)或设置(set)HTML属性、CSS样式、元素内容和位置高宽的值。该节讲述这些方法。首先,让我们对jQuery中的getter和setter方法有个概要理解:
·jQuery使用同一个方法既当getter用又做setter用,而不是定义一对方法。如果传入一个新值给该方法,则它设置此值;如果没指定值,则它返回当前值。
·用做setter时,这些方法会给jQuery对象中的每一个元素设置值,然后返回该jQuery对象以方便链式调用。
·用做getter时,这些方法只会查询元素集中的第一个元素,返回单个值。(如果要遍历所有元素,请使用map()。)getter不会返回调用自身的jQuery对象,因此它只能出现在链式调用的末尾。
·用做setter时,这些方法经常接受对象参数。在这种情况下,该对象的每一个属性都指定一个需要设置的名/值对。
·用做setter时,这些方法经常接受函数参数。在这种情况下,会调用该函数来计算需要设置的值。调用该函数时的this值是对应的元素,第一个参数是该元素的索引值,当前值则作为第二参数传入。
阅读本节接下来的内容时,请将对getter和setter的概要理解牢记于心。下面的每一节会讲述jQuery getter/setter方法中的一个重要类别。
19.2.1 获取和设置HTML属性
attr()方法是jQuery中用于HTML属性的getter/setter,它符合上面描述的概要理解中的每一条。attr()处理浏览器的兼容性和一些特殊情况,还让HTML属性名和JavaScript属性名可以等同使用(当二者存在差异时)。例如,可以使用"for"也可以使用"htmlFor",可以使用"class"也可以使用"className"。一个相关函数是removeAttr(),可用来从所有选中元素中移除某个属性。下面是一些例子:
$("form").attr("action");//获取第一个form元素的action属性
$("#icon").attr("src","icon.gif");//设置src属性
$("#banner").attr({src:"banner.gif",//一次性设置4个属性
alt:"Advertisement",
width:720,height:64});
$("a").attr("target","_blank");//使所有链接在新窗口中打开
$("a").attr("target",function(){//使站内链接在本窗口中打开,并且让
if(this.host==location.host)return"_self"
else return"_blank";//非站内链接在新窗口中打开
});
$("a").attr({target:function(){…}});//可以像这样传入函数
$("a").removeAttr("target");//让所有链接在本窗口中打开
19.2.2 获取和设置CSS属性
css()方法和attr()方法很类似,只是css()方法作用于元素的CSS样式,而不是元素的HTML属性。在获取样式值时,css()返回的是元素的当前样式(或称为“计算”样式,参考16.4节):返回值可能来自style属性也可能来自样式表。注意:不能获取复合样式的值,比如"font"或"margin"。而应该获取单个样式的值,比如"font-weight"、"font-family"、"margin-top"或"margin-left"。在设置样式时,css()方法会将样式简单添加到该元素的style属性中。css()方法允许在CSS样式名中使用连字符("background-color")或使用驼峰格式JavaScript样式名("backgroundColor")。在获取样式值时,css()会把数值转换成带有单位后缀的字符串返回。而在设置样式值时,则会将数值转化成字符串,在必要时添加"px"(像素)后缀:
$("h1").css("font-weight");//获取第一个<h1>的字体重量
$("h1").css("fontWeight");//也可以采用驼峰格式
$("h1").css("font");//错误:不可获取复合样式
$("h1").css("font-variant",//将样式设置在所有<h1>元素上
"smallcaps");
$("div.note").css("border",//设置复合样式是OK的
"solid black 2px");
$("h1").css({backgroundColor:"black",//一次设置多个样式
textColor:"white",//也可以用驼峰格式的名称[4]
fontVariant:"small-caps",//对象属性
padding:"10px 2px 4px 20px",
border:"dotted black 4px"});//让所有<h1>的字体大小增加25%
$("h1").css("font-size",function(i,curval){
return Math.round(1.25*parseInt(curval));
});
19.2.3 获取和设置CSS类
回忆一下,class属性值(在JavaScript里通过className访问)会被解析成为一个由空格分隔的CSS类名列表。通常,我们想要往列表中添加、删除某一项,或判断某一项是否在列表中,而不是将该列表替换为另一个。因此,jQuery定义了一些便捷方法用来操作class属性。addClass()和removeClass()用来从选中元素中添加和删除类。toggleClass()的用途是,当元素还没有某些类时,给元素添加这些类;反之,则删除。hasClass()用来判断某类是否存在。下面是一些例子:
//添加CSS类
$("h1").addClass("hilite");//给所有<h1>元素添加一个类
$("h1+p").addClass("hilite first");//给<h1>后面的<p>添加两个类
$("section").addClass(function(n){//传递一个函数用来给匹配的
return"section"+n;//每一个元素添加自定义类
});//删除CSS类
$("p").removeClass("hilite");//从所有<p>元素中删除一个类
$("p").removeClass("hilite first");//允许一次删除多个类
$("section").removeClass(function(n){//从元素中删除自定义类
return"section"+n;
});
$("div").removeClass();//删除所有<div>中的所有类
//切换CSS类
$("tr:odd").toggleClass("oddrow");//如果该类不存在则添加
//如果存在则删除
$("h1").toggleClass("big bold");//一次切换两个类
$("h1").toggleClass(function(n){//切换用函数计算出来的类
return"big bold h1-"+n;
});
$("h1").toggleClass("hilite",true);//作用类似addClass
$("h1").toggleClass("hilite",false);//作用类似removeClass
//检测CSS类
$("p").hasClass("first")//是否所有p元素都有该类?
$("#lead").is(".first")//功能和上面类似
$("#lead").is(".first.hilite")//is()比hasClass()更灵活
注意:hasClass()不如addClass()、removeClass()、toggleClass()灵活。hasClass()只能接受单个类名作为参数,并且不支持函数参数。当选中元素中的任意元素有指定CSS类时,hasClass()返回true;如果任何元素都没有,则返回false。19.1.2节描述的is()方法更灵活,可用来做同样的事。
jQuery的这些方法和16.5节讲的classList方法类似,只是jQuery的方法可以工作在所有浏览器中,而不仅仅是那些支持HTML5 classList属性的浏览器。此外,毫无疑问,jQuery的方法可操作多个元素并支持链式调用。
19.2.4 获取和设置HTML表单值
val()方法用来设置和获取HTML表单元素的value属性,还可用于获取和设置复选框、单选按钮以及<select>元素的选中状态:
$("#surname").val()//获取surname文本域的值
$("#usstate").val()//从<select>中获取单一值
$("select#extras").val()//从<select multiple>中获取一组值
$("input:radio[name=ship]:checked").val()//获取选中的单选按钮的值
$("#email").val("Invalid email address")//给文本域设置值
$("input:checkbox").val(["opt1","opt2"])//选中带有这些名字或值的复选框
$("input:text").val(function(){//重置所有文本域为默认值
return this.defaultValue;
})
19.2.5 设置和获取元素内容
text()和html()方法用来获取和设置元素的纯文本或HTML内容。当不带参数调用时,text()返回所有匹配元素的所有子孙文本节点的纯文本内容。该方法甚至可以工作在不支持textContent或innerText属性(参考15.5.2节)的浏览器中。
如果不带参数调用html()方法,它会返回第一个匹配元素的HTML内容。jQuery使用innerHTML属性来实现:x.html()和x[0].innerHTML一样高效。
如果传入字符串给text()或html(),该字符串会用做该元素的纯文本或格式化的HTML文本内容,它会替换掉所有存在的内容。和其他setter方法一样,我们还可以传入函数,该函数用来计算出表示新内容的字符串:
var title=$("head title").text()//获取文档标题
var headline=$("h1").html()//获取第一个<h1>元素的html
$("h1").text(function(n,current){//给每一个标题添加章节号
return"§"+(n+1)+":"+current
});
19.2.6 获取和设置元素的位置高宽
在15.8节中我们知道通过一些技巧可以正确获取元素的大小和位置,尤其当浏览器不支持getBoundingClientRect()(参考15.8.2节)时。使用jQuery方法可以更简单地获取元素的大小和位置,并兼容所有浏览器。注意:本节描述的所有方法都是getter,只有少部分可用做setter。
使用offset()方法可以获取或设置元素的位置。该方法相对文档来计算位置值,返回一个对象,带有left和top属性,用来表示X和Y坐标。如果传入带有这些属性的对象给该方法,它会给元素设置指定的位置。在有必要时,会设置CSS的position属性来使得元素可定位:
var elt=$("#sprite");//需要移动的元素
var position=elt.offset();//获取当前位置
position.top+=100;//改变Y坐标
elt.offset(position);//设置新位置
//将所有<h1>元素向右移动,移动的距离取决于它们在文档中的位置
$("h1").offset(function(index,curpos){
return{left:curpos.left+25*index,top:curpos.top};
});
position()方法很像offset()方法,但它只能用做getter,它返回的元素位置是相对于其偏移父元素的,而不是相对于文档的。在15.8.5节中,我们知道任何元素都有一个offsetParent属性,其位置是相对的。定位元素总会当做其子孙元素的偏移父元素,但在某些浏览器下,也会把表格单元等其他元素当成偏移父元素。jQuery只会把定位元素作为偏移父元素,jQuery对象的offsetParent()方法则会把每个元素映射到最近的定位祖先元素或<body>元素。注意这些方法的名字并不很恰当:offset()返回元素的绝对位置,用相对于文档的坐标来表示。而position()则返回相对于元素的offsetParent()的偏移量。
用于获取元素宽度的getter有3个,获取高度的也有3个。width()和height()方法返回基本的宽度和高度,不包含内边距、边框和外边距。innerWidth()和innerHeight()返回元素的宽度和高度,包含内边距的宽度和高度(“内”表示这些方法度量的是边框以内的尺寸)。outerWidth()和outerHeight()通常返回的是包含元素内边距和边框的尺寸。如果向两个方法中的任意一个传入true值,它们还可以返回包含元素外边距的尺寸。下面的代码展现了如何获取一个元素的4种不同宽度:
var body=$("body");
var contentWidth=body.width();
var paddingWidth=body.innerWidth();
var borderWidth=body.outerWidth();
var marginWidth=body.outerWidth(true);
var padding=paddingWidth-contentWidth;//左内边距和右内边距的和
var borders=borderWidth-paddingWidth;//左边框和右边框的和
var margins=marginWidth-borderWidth;//左外边距和右外边距的和
width()和height()方法拥有其他4个方法(以inner和outer开头的方法)所没有的特性。首先,当jQuery对象的第一个元素是Window或Document对象时,width()和height()返回的是窗口的视口大小或文档的整体尺寸。其他方法只适用于元素,不适用窗口和文档。
另一个特性是width()和height()方法可以是setter也可以是getter。如果传递值给这些方法,它们会给jQuery对象中的每一个元素设置宽度或高度。(注意:不能给Window和Document对象设置宽度或高度。)如果传入数值,会把它当成单位为像素的尺寸。如果传入字符串,会把它用做CSS的width和height属性的值,因此可以使用任何CSS单位。最后,和其他setter类似,可以传入函数,用来计算要设置的宽度或高度。
在width()和height()的getter和setter行为之间有个小的不对称。用做getter时,这些方法返回元素的内容盒子的尺寸,不包括内边距、边框和外边距。用做setter时,它们只是简单设置CSS的width和height属性。默认情况下,这些属性也指定内容盒子的大小。但是,如果一个元素的CSS box-sizing属性(参考16.2.3节)设置为border-box,则width()和height()方法设置的尺寸包括内边距和边框。对于使用context-box作为盒模型的元素e,调用$(e).width(x).width()返回x值。然而,对于使用border-box模型的元素,这种情况下一般不会返回x值。
与位置尺寸相关的最后一对jQuery方法是scrollTop()和scrollLeft(),可获取或设置元素的滚动条位置。这些方法可用在Window对象以及Document元素上,当用在Document对象上时,会获取或设置存放该Document的Window对象的滚动条位置。与其他setter不同,不可传递函数给scrollTop()或scrollLeft()。
可使用scrollTop()作为getter和setter,与height()方法一起,来定义一个方法:根据指定的页面数向上或向下滚动窗口:
//根据页面数n来滚动窗口。n可以是分数或负数
function page(n){
var w=$(window);//将window封装为jQuery对象
var pagesize=w.height();//得到页面大小
var current=w.scrollTop();//得到当前滚动条位置
w.scrollTop(current+n*pagesize);//设置新的滚动条位置
}
19.2.7 获取和设置元素数据
jQuery定义了一个名为data()的getter/setter方法,可用来设置或获取与文档元素、Document或Window对象相关联的数据。可以将数据与任意元素关联是很重要和强大的一项能力:这是jQuery的事件处理程序注册和效果队列机制的基础,有时,我们还会在自己的代码中使用data()方法。
需将数据与jQuery对象中的元素关联,传递名称和值两个参数给data()方法即可。还可以传递一个对象给data()setter,此时,该对象的每一个属性都将用做名/值对,用来与jQuery对象的元素关联。注意,传递对象给data()时,该对象的属性将替换掉与元素相关联的旧数据。与很多其他setter方法不同,data()不接受函数参数。当将函数作为第二参数传递给data()时,该函数会存储,就和其他值一样。
当然,data()方法也可以用做getter。当不带参数调用时,它会返回一个对象,含有与jQuery对象中的第一个元素相关联的所有名/值对。当传入一个字符串参数调用data()时,它会返回对于第一个元素与该字符串参数相关联的数据值。
removeData()方法用来从元素中删除数据。(使用data()设置值为null或undefined和实际上删除该值不是同一回事。)如果传递字符串给removeData(),该方法会删除元素中与该字符串相关联的值。如果不带参数调用removeData(),它会删除与元素相关联的所有数据。
$("div").data("x",1);//设置一些数据
$("div.nodata").removeData("x");//删除一些数据
var x=$('#mydiv').data("x");//获取一些数据
jQuery还定义了data()和removeData()方法的工具函数形式。要给单一元素e关联数据,可以使用data()的方法形式,也可以使用其函数形式:
$(e).data(…)//方法形式
$.data(e,…)//函数形式
jQuery的数据框架没有将元素数据当做元素的属性来存储,但它的确需要给元素添加一个特殊属性用来与数据关联。由于某些浏览器不允许添加属性到<applet>、<object>和<embed>元素中,因此jQuery根本不允许给这些类型的元素关联数据。
19.3 修改文档结构
在19.2.5节中我们知道html()和text()方法可用来设置元素内容。本节将讲述能对文档做出更复杂修改的方法。HTML文档表示为一棵节点树,而不是一个字符的线性序列,因此插入、删除、替换操作不会像操作字符串和数组一样简单。接下来的内容会阐释用于文档修改的jQuery的各种方法。
19.3.1 插入和替换元素
让我们从基本的插入和替换方法开始。下面演示的每一个方法都接受一个参数,用于指定需要插入文档中的内容。该参数可以是用于指定新内容的纯文本或HTML字符串,也可以是jQuery对象、元素或文本节点。根据调用的方法不同,会在选中元素的里面、前面或后面位置中插入内容。如果待插入的内容是已存在于文档中的元素,会从当前位置移走它。如果它需要插入多次,在必要时会复制该元素。这些方法都返回调用自身的jQuery对象。注意,在replaceWith()运行后,该jQuery对象中的元素将不再存在于文档中:
$("#log").append("<br/>"+message);//在#log元素的结尾处添加内容
$("h1").prepend("§");//在每个<h1>的起始处添加章节标识
$("h1").before("<hr/>");//在每个<h1>的前面添加水平线
$("h1").after("<hr/>");//在每个<h1>的后面添加水平线
$("hr").replaceWith("<br/>");//替换<hr/>元素为<br/>
$("h2").each(function(){//将<h2>替换为<h1>,保持内容不变
var h2=$(this);
h2.replaceWith("<h1>"+h2.html()+"</h1>");
});//after()和before()也可用在文本节点上
//这是给每个<h1>的开头添加章节标识的另一种方法
$("h1").map(function(){return this.firstChild;}).before("§");
这5个用于结构修改的方法都接受函数参数,用来计算出需要插入的值。和平常一样,如果传入函数,该函数会为每个选中元素调用一次。this值将指向该元素,在jQuery对象中元素的索引值将作为第一参数。对于append()、prepend()和replaceWith(),第二参数将是该元素当前内容的HTML字符串形式。对于before()和after(),该函数在调用时没有第二参数。
上面演示的5个方法都在目标元素上调用,并传入需要插入的内容作为参数。这5个方法中的每一个都可以找到另一个方法来实现差不多一样的功能,只要采用不同的方式操作即可:在内容上调用,并传入目标元素作为参数。下表展示了这些方法对:
在上面的例子代码中演示的方法在上表第二列中。第三列中的方法会在下面演示。要理解这些方法对,有几个重要事项:
·如果传递字符串给第二列中的方法,会把它当做需要插入的HTML字符串。如果传递字符串给第三列中的方法,会把它当做选择器,用来标识目标元素。(也可以直接传入jQuery对象、元素或文本节点来指明目标元素。)
·第三列中的方法不接受函数参数,第二栏中的方法可以。
·第二列中的方法返回调用自身的jQuery对象。该jQuery对象中的元素有可能有新内容或新兄弟节点,但这些元素自身并没有修改。第三列中的方法在插入的内容上调用,返回一个新的jQuery对象,表示插入操作后的新内容。特别注意,当内容被插入多个地方时,返回的jQuery对象将为每一个地方保留一个元素。
上面列举了不同点,下面的代码将实现与上面的代码一样的操作,使用的是第三列中的方法来替代第二列中的。注意在第二行的代码中不能传入纯文本(不带任何<>括号来标识它为HTML)给$()方法——它会被当做选择器。因此,必须显式创建需要插入的文本节点:
$("<br/>+message").appendTo("#log");//添加html到#log中
$(document.createTextNode("§")).prependTo("h1");//给所有<h1>添加文本节点
("<hr/>").insertBefore("h1");//在所有<h1>前面插入水平线
$("<hr/>").insertAfter("h1");//在所有<h1>后面插入水平线
$("<br/>").replaceAll("hr");//将<hr/>替换为<br/>
19.3.2 复制元素
如上所述,如果插入的元素已经是文档的一部分,这些元素只会简单地移动而不是复制到新位置。如果元素到插入不止一个位置,jQuery在需要时会复制元素,但是当只插入一个位置时,是不会进行复制操作的。如果想复制元素到新位置而不是移动它,必须首先用clone()方法来得到一个副本。clone()创建并返回每一个选中元素(包含元素所有子孙)的一个副本。返回的jQuery对象的元素还不是文档的一部分,可以用上一节中的方法将其插入文档中:
//给文档结尾添加一个带有"linklist"id的新div
$(document.body).append("<div id='linklist'><h1>List of Links</h1></div>");//将文档中的所有链接复制并插入该新div中
$("a").clone().appendTo("#linklist");//在每一个链接后面插入<br/>元素,使其以独立行显示
$("#linklist>a").after("<br/>");
clone()不会复制事件处理程序(见19.4节)和与元素关联的其他数据(见19.2.7节)。如果想复制这些额外的数据,请传入true参数。
19.3.3 包装元素
插入HTML文档的另一种类型涉及在一个或多个元素中包装新元素。jQuery定义了3个包装函数。wrap()包装每一个选中元素。wrapInner()包装每一个选中元素的内容。wrapAll()则将选中元素作为一组来包装。这些方法通常传入一个新创建的包装元素或用来创新包装元素的HTML字符串。如果需要,HTML字符串可以包含多个嵌套元素,但必须是单个最内层的元素。如果传入函数给这些方法,它会在每个元素的上下文中调用一次,this指向该元素,元素的索引值是唯一参数,应该返回需要返回表示包装元素的字符串、Element或jQuery对象。下面是些例子:
//用<i>元素包装所有<h1>元素
$("h1").wrap(document.createElement("i"));//产生<i><h1>…</h1></i>//包装所有<h1>元素的内容,使用字符串参数更简单
$("h1").wrapInner("<i/>");//产生<h1><i>…</i></h1>
//将第一个段落包装在一个锚点和div里
$("body>p:first").wrap("<a name='lead'><div class='first'></div></a>");//将所有其他段落包装在另一个div里
$("body>p:not(:first)").wrapAll("<div class='rest'></div>");
19.3.4 删除元素
除了插入和替换操作,jQuery还定义了用来删除元素的方法。empty()会删除每个选中元素的所有子节点(包括文本节点),但不会修改元素自身。对比而言,remove()方法会从文档中移除选中元素(以及所有元素的内容)。通常不带参数调用remove(),此时会从文档中移除jQuery对象中的所有元素。然而,如果传入一个参数,该参数会被当成选择器,jQuery对象中只有匹配该选择器的元素才会被移除。(如果只想将元素从选中元素集中移除,而不需要从文档中移除时,请使用filter()方法,该方法会在19.8.2节讲述。)注意,将元素重新插入文档前,移除操作是没有必要的:简单地将其插入新位置,就会移动它们。
remove()方法会移除所有事件处理程序(参考19.4节)以及可能绑定到被移除元素上的其他数据(参见19.2.7节)。detach()方法和remove()类似,但不会移除事件处理程序和数据。想临时从文档中移除元素以便后续再次插入时,detach()可能会更有用。
最后,unwrap()方法可以用来实现元素的移除,其方式是wrap()或wrapAll()方法的反操作:移除每一个选中元素的父元素,不影响选中元素及其兄弟节点。也就是说,对于每一个选中元素,它替换该元素的父节点为父节点的子节点。与remove()和detach()不同,unwrap()不接受可选的选择器参数。
19.4 使用jQuery处理事件
在第17章我们知道,处理事件时有一个难点是IE(IE9以下)实现了一个与所有其他浏览器不同的事件API。为了解决这一难点,jQuery定义了一个统一事件API,可工作在所有浏览器中。jQuery API具有简单的形式,比标准或IE的事件API更容易使用。jQuery API还具有更复杂、功能更齐全的形式,比标准API更强大。接下来的章节会详细阐述。
19.4.1 事件处理程序的简单注册
jQuery定义了简单的事件注册方法,可用于常用和普适的每一个浏览器事件。比如,给单击事件注册一个事件处理程序,只要调用click()方法:
//单击任意<p>时,使其背景变成灰色
$("p").click(function(){$(this).css("background-color","gray");});
调用jQuery的事件注册方法可以给所有选中元素注册处理程序。很明显,这比使用addEventListener()或attachEvent()一次注册一个事件处理程序简单很多。
下面是jQuery定义的简单事件处理程序注册的方法:
这些注册方法的大部分都用于在第17章已经熟悉的常见事件类型。下面按顺序给出一些注意事项。focus和blur事件不支持冒泡,但focusin和focusout事件支持,jQuery确保这些事件在所有浏览器下都支持。相反地,mouseover和mouseout事件支持冒泡,但这经常不方便,因为很难知道鼠标是从自己感兴趣的元素中移开了,还只是从该元素的子孙元素中移开了。mouseenter和mouseleave是非冒泡事件,可以解决刚才的问题。这几个事件类型最初是由IE引入的,jQuery确保它们可在所有浏览器下正确工作。
resize和unload事件类型只在Window对象中触发,如果想要给这两个事件类型注册处理程序,应该在$(window)上调用resize()和unload()方法。scroll()方法经常也用于$(window)对象上,但它也可以用在有滚动条的任何元素上(比如,当CSS的overflow属性设置为"scroll"或"auto"时)。load()方法可在$(window)上调用,用来给窗口注册加载事件处理程序,但经常更好的选择是,直接将初始化函数传给19.1.1节所示的$()。当然,还可以在iframe和图片上使用load()方法。注意,用不同的参数调用时,load()还可用于加载新内容(通过脚本化HTTP)到元素中——请阅读19.6.1节。error()方法可用在<img>元素上,用来注册当图片加载失败时调用的处理程序。error()不应该用于设置14.6节描述的窗口的onerror属性。
除了这些简单的事件注册方法外,还有两个特殊形式的方法,有时很有用。hover()方法用来给mouseenter和mouseleave事件注册处理程序。调用hover(f,g)就和调用mouseenter(f)然后调用mouseleave(g)一样。如果仅传入一个参数给hover(),该参数函数会同时用做enter和leave事件的处理程序。
另一个特殊的事件注册方法是toggle()。该方法将事件处理程序函数绑定到单击事件。可指定两个或多个处理程序函数,当单击事件发生时,jQuery每次会调用一个处理程序函数。例如,如果调用toggle(f,g,h),第一次单击事件触发时,会调用函数f(),第二次会调用g(),第三次则调用h(),然后调用f()来处理第四次单击事件。小心使用toggle():我们将在19.5.1节看到,该方法可用来显示或隐藏选中元素(也就是说,切换选中元素的可见性)。
在19.4.4节中,我们会学到其他更通用的方式来注册事件处理程序,本节最后,让我们再学会一个更简单且更便捷的处理程序注册方法。
回忆下,可以传递HTML字符串给$()方法来创建该字符串所描述的元素,还可传入一个对象(当做第二个参数),该对象由属性组成,这些属性可设置到新创建的元素上。这第二个参数可以是传递给attr()方法的任意对象。此外,如果这些属性中有任何一个与上面列举的事件注册方法同名,该属性值会被当做处理程序函数,并注册为命名事件类型的处理程序。例如:
$("<img/>",{
src:image_url,
alt:image_description,
className:"translucent_image",
click:function(){$(this).css("opacity","50%");}
});
19.4.2 jQuery事件处理程序
上面例子中的事件处理程序函数被当做是不带参数以及不返回值的。像这样书写事件处理程序非常正常,但jQuery调用每一个事件处理程序时的确传入了一个或多个参数,并且对处理程序的返回值进行了处理。需要知道的最重要的一件事情是,每个事件处理程序都传入一个jQuery事件对象作为第一个参数。该对象的字段提供了与该事件相关的详细信息(比如鼠标指针的坐标)。标准事件对象的属性在第17章描述过。jQuery模拟标准Event对象,即便在不支持的标准事件对象的浏览器中(像IE8及其以下),jQuery事件对象在所有浏览器上拥有一组相同的字段。这在19.4.3节会详细讲述。
通常,调用事件处理程序时只带有事件对象这个唯一参数。如果用trigger()(参见19.4.6节)显式触发事件,可以传入额外的参数数组。这样做时,这些参数会在第一个事件对象参数之后传递给事件处理程序。
不管它们是如何注册的,jQuery事件处理程序函数的返回值始终有意义。如果处理程序返回false,与该事件相关联的默认行为,以及该事件接来下的冒泡都会被取消。也就是说,返回false等同于调用Event对象的preventDefault()和stopPropagation()方法。同样,当事件处理程序返回一个值(非undefined值)时,jQuery会将该值存储在Event对象的result属性中,该属性可以被后续调用的事件处理程序访问。
19.4.3 jQuery事件对象
jQuery通过定义自己的Event对象来隐藏浏览器之间的实现差异。当一个jQuery事件处理程序被调用时,总会传入一个jQuery事件对象作为其第一个参数。jQuery事件对象主要以W3C标准为基准,同时它也实现了一些实际的事件标准。jQuery会将以下所有字段从原生Event对象中复制到jQuery Event对象上(尽管对于特定事件类型来说,有些字段值为undefined):
除了这些属性,Event对象还定义了以下方法:
这些事件属性和方法中的大部分在第17章介绍过,并在第四部分的ref-Event中有详细文档说明。对于一部分字段,jQuery做了特殊处理,使其在所有浏览器中的行为一致,值得我们留意:
metaKey
如果原生事件对象没有metaKey属性,jQuery会使其与ctrlKey属性的值一样。在Mac OS中,Command键设置meta键的属性。
pageX,pageY
如果原生事件对象没有定义这两个属性,但定义了鼠标指针的视口坐标clientX和clientY,jQuery会计算出鼠标指针的文档坐标并把它们存储在pageX和pageY中。
target,currentTarget,relatedTarget
target属性表示在其上发生事件的文档元素。如果原生事件对象的目标是文本节点,jQuery返回的目标会替换为包含该文本节点的元素。currentTarget是当前正在执行的事件处理程序所注册的元素,与this应该始终一样。
如果currentTarget和target不一样,那么正在处理的事件是从触发它的元素冒泡上来的,此时使用is()方法(参见19.1.2节)来检测target元素可能会很有用:
if($(event.target).is("a"))return;//忽略在链接上启动的事件
涉及mouseover和mouseout等过渡事件时,relatedTarget表示其他元素。例如,对于mouseover事件,relatedTarget属性指鼠标指针移开的元素,target则是鼠标指针悬浮的元素。如果原生事件对象没有定义relatedTarget但定义了toElement和fromElement,则会从这些属性中得到relatedTarget。
timeStamp
事件发生时的时间,单位是毫秒,由Date.getTime()方法返回。这个字段是jQuery自身设置的,可以解决Firefox中一个长期存在的bug。
which
这是一个非标准事件属性,jQuery做了统一化处理,使其可以用来指明在事件发生期间,按下的是哪个鼠标按钮或键盘按键。对键盘事件来说,如果原生事件没有定义which,但定义了charCode或keyCode,which将被设置为定义过的charCode或keyCode。对鼠标事件来说,如果which没有定义但定义了button属性,会根据button的值来设置which。0表示没有按钮按下。1表示鼠标左键按下,2表示鼠标中键按下,3表示鼠标右键按下。(注意,单击鼠标右键时,有些浏览器不会产生鼠标事件。)
此外,jQuery Event对象的以下字段是特定于jQuery添加的,有时会很有用:
data
如果注册事件处理程序时指定了额外的数据(参见19.4.4节),处理程序可以用该字段的值来访问。
handler
当前正被调用的事件处理程序函数的引用。
result
该事件最近调用的处理程序的返回值,忽略没有返回值的处理程序。
originalEvent
浏览器生成的原生事件对象的引用。
19.4.4 事件处理程序的高级注册
我们已经看到,jQuery定义了相当多简单的方法来注册事件处理程序。所有这些方法都是简单地调用单一的、更复杂的方法bind()来为命名的事件类型绑定处理程序,该处理程序会绑定到jQuery对象中的每一个元素上。直接使用bind()可以让我们使用事件注册的高级特性,这些特性在较简单的方法上是不可用的[5]。
在最简形式下,bind()需要一个事件类型字符串作为其第一个参数,以及一个事件处理程序函数作为其第二个参数。事件注册的简单方法使用该形式的bind()。例如,调用$('p').click(f)等价:
$('p').bind('click',f);
调用bind()时还可以带有三个参数。在这种形式下,事件类型是第一个参数,处理程序函数是第三个参数。在这两个参数中间可以传入任何值,jQuery会在调用处理程序前,将指定的值设置为Event对象的data属性。通过这种方式传递额外的数据给处理程序,不需要使用闭包,有时很有用。
bind()还有其他高级特性。如果第一个参数是由空格分隔的事件类型列表,则处理程序函数会为每一个命名的事件类型注册。例如,调用$('a').hover(f)(参见19.4.1节)等同于:
$('a').bind('mouseenter mouseleave',f);
bind()的另一个重要特性是允许为注册的事件处理程序指定命名空间。这使得可以定义处理程序组,能方便后续触发或卸载特定命名空间下的处理程序。处理程序的命名空间对于开发可复用jQuery代码的类库或模块的程序员来说特别有用。事件命名空间类似CSS的类选择器。要绑定事件处理器到命名空间中时,添加句点(.)和命名空间名到事件类型字符串中即可:
//作为mouseover处理程序在在命名空间"myMod"中把f绑定到所有元素
$('a').bind('mouseover.myMod',f);
甚至还可以给处理程序分配多个命名空间,如下所示:
//在命名空间"myMod"和"yourMod"中作为mouseout处理程序绑定f
$('a').bind('mouseout.myMod.yourMod',f);
bind()的最后一个特性是,第一个参数可以是对象,该对象把事件名映射到处理程序函数。再次使用hover()方法来举例,调用$('a').hover(f,g)等价于:
$('a').bind({mouseenter:f,mouseleave:g});
当使用bind()的这种形式时,传入对象的属性名可以是空格分隔的事件类型的字符串,也可包括命名空间。如果在第一个对象参数之后还指定了第二个参数,其值会用做每一个事件绑定的数据参数。
jQuery还有另一个事件处理程序注册方法。调用one()方法就和bind()一样,二者的工作原理也类似,除了在调用事件处理程序之后会自动注销它。这意味着,和该方法名字暗示的一样,使用one()注册的事件处理器永远只会触发一次。
使用addEventListener()(参见17.2.3节)可以注册捕获事件的处理程序,bind()和one()没有该特性。IE(IE9以下版本)不支持捕获处理程序,jQuery不打算模拟该特性。
19.4.5 注销事件处理程序
用bind()(或任何更简单的事件注册方法)注册事件处理程序后,可以使用unbind()来注销它,以避免在将来的事件中触发它。(注意,unbind()只注销用bind()和相关jQuery方法注册的事件处理程序。通过addEventListener()或IE的attachEvent()方法注册的处理器不会注销,并且不会移除通过onclick和onmouseover等元素属性定义的处理程序。)不带参数时,unbind()会注销jQuery对象中所有元素的(所有事件类型的)所有事件处理程序:
$('*').unbind();//从所有元素中移除所有jQuery事件处理程序
带有一个字符串参数时,由该字符串指明的事件类型(可以是多个,当字符串含有多个名字时)的所有处理程序会从jQuery对象的所有元素中取消绑定:
//从所有<a>元素中取消绑定所有mouseover和mouseout处理程序
$('a').unbind("mouseover mouseout");
这是很粗劣的方式,不应该在模块化代码中使用,因为模块的使用者有可能使用其他模块,在其他模块中有可能在相同的元素上给相同的事件类型注册了其他处理程序。如果模块使用命名空间来注册事件处理程序,则可以使用unbind(),传入一个参数,来做到只注销命名空间下的处理程序:
//取消绑定在"myMod"命名空间下的所有mouseover和mouseout处理程序
$('a').unbind("mouseover.myMod mouseout.myMod");//取消绑定在"myMod"命名空间下的所有事件类型的处理程序
$('a').unbind(".myMod");//取消绑定同时在"ns1"和"ns2"命名空间下的单击处理程序
$('a').unbind("click.ns1.ns2");
如果想小心地只取消绑定自己注册的事件处理程序,但没有使用命名空间,必须保留事件处理程序函数的一个引用,并使用unbind()带两个参数的版本。在这种形式下,第一个参数是事件类型字符串(不带命名空间),第二个参数是处理程序函数:
$('#mybutton').unbind('click',myClickHandler);
通过这种方式调用时,unbind()从jQuery对象的所有元素中注销特定类型的指定事件处理程序函数。注意,即便使用有3个参数的bind()通过额外的数据值注册事件处理程序,也可以使用有两个参数的unbind()事件来取消绑定它们。
可以传递单一对象参数给unbind()。在这种情况下,unbind()会轮询为该对象的每一属性调用一次。属性名会用做事件类型字符串,属性值会用做处理程序函数:
$('a').unbind({//Remove specific mouseover and mouseout handlers
mouseover:mouseoverHandler,
mouseout:mouseoutHandler
});
最后,还有一种方式来调用unbind()。如果传递一个jQuery Event对象给unbind(),它会取消绑定传入事件的事件处理程序。调用unbind(e v)等价于unbind(e v.type,ev.handler)。
19.4.6 触发事件
当用户使用鼠标、键盘或触发其他事件类型时,注册的事件处理程序会自动调用。然而,如果能手动触发事件,有时会很有用。手动触发事件最简单的方式是不带参数调用事件注册的简单方法(比如click()或mouseover())。与很多jQuery方法可以同时用做做getter和setter一样,这些事件方法在带有一个参数时会注册事件处理程序,不带参数调用时则会触发事件处理程序。例如:
$("#my_form").submit();//就和用户单击提交按钮一样
上面的submit()方法自己合成了一个Event对象,并触发了给submit事件注册的所有事件处理程序。如果这些事件处理程序都没有返回false或调用Event对象的preventDefault(),实际上将提交该表单。注意,通过这种方式手动调用时,冒泡事件依旧会冒泡。这意味着触发一组选中元素的事件,同时也会触发这些元素祖先节点的处理程序。
需要特别注意,jQuery的事件触发方法会触发所有使用jQuery事件注册方法注册的处理程序,也会触发通过onsubmit等HTML属性或Element属性定义的处理程序。但是,不能手动触发使用addEventListener()或attachEvent()注册的事件处理程序(当然,在真实事件触发时,这些处理程序依旧会调用。)
同时需要注意,jQuery的事件触发机制是同步的——不涉及事件队列。当触发一个事件时,在调用的触发方法返回之前,事件处理程序会立刻调用。如果触发了一个单击事件,被触发的处理程序又触发了一个submit事件,所有匹配的submit处理程序会在调用下一个单击处理器之前调用。
绑定和触发事件时,submit()这种方法很便捷,但就如jQuery定义了一个更通用的bind()方法一样,jQuery也定义了一个更通用的trigger()方法。通常,调用trigger()时会传入事件类型字符串作为第一个参数,trigger()会在jQuery对象中的所有元素上触发为该类型事件注册的所有处理程序。因此,上面的submit()调用等价于:
$("#my_form").trigger("submit");
与bind()和unbind()方法不同,在传入的字符串中不能指定多个事件类型。然而,与bind()和unbind()一样的是,可以指定事件命名空间来触发仅在该命名空间中定义的处理程序。如果只想触发没有命名空间的事件处理程序,在事件类型后添加一个感叹号就行。通过onclick等属性注册的处理程序被认为是没有命名空间的:
$("button").trigger("click.ns1");//触发某个命名空间下的单击处理程序
$("button").trigger("click!");//触发没有命名空间的单击处理程序
除了给trigger()传入事件类型字符串作为第一个参数,还可以传入Event对象(或任何有type属性的对象)。type属性会用来判断触发什么类型的处理程序。如果传入的是jQuery事件对象,该对象会传递给触发的处理程序。如果传入的是普通对象,会创建一个新的jQuery Event对象,普通对象的属性会添加到新对象中。这样,可以很容易传递额外数据给事件处理程序:
//button1的单击处理程序触发button2上的相同事件
$('#button1').click(function(e){$('#button2').trigger(e);});//触发事件时,添加额外的属性给事件对象
$('#button1').trigger({type:'click',synthetic:true});//该处理程序检测额外属性来区分是真实事件还是虚假事件
$('#button1').click(function(e){if(e.synthetic){…};});
给事件处理程序传递额外数据的另一种方式是,在手动触发事件时,给trigger()传入第二个参数。给trigger()传入的第二个参数会成为每个触发的事件处理程序的第二个参数。如果传入数组作为第二个参数,数组的每一项会作为独立参数传递给触发的处理程序:
$('#button1').trigger("click",true);//传入单一额外参数
$('#button1').trigger("click",[x,y,z]);//传入三个额外参数
有时,会想触发给定事件类型的所有处理程序,而不管这些处理程序是绑定到什么文档元素上的。这时可以使用$('*')来选中所有元素,然后对结果调用trigger(),可是这样做非常低效。更好的方式是,使用jQuery.event.trigger()工具函数,来全局触发事件。该函数接受的参数和trigger()方法一样,但在整个文档中触发指定事件类型的事件处理程序时更高效。注意,以这种方式触发的“全局事件”不会冒泡,并且只会触发使用jQuery方法注册的处理程序(不包括用onclick等DOM属性注册的事件处理程序)。
trigger()(及调用它的便捷方法)在调用事件处理程序后,会执行与触发事件相关联的默认操作(假设事件处理程序没有返回false或调用事件对象的preventDefault())。例如,触发一个<form>元素的submit事件时,trigger()会调用该表单的submit()方法,如果触发一个元素的focus事件,trigger()会调用该元素的focus()方法。
如果想调用事件处理程序,但不执行默认操作,可以使用triggerHandler()替代trigger()。该方法和trigger()类似,除了首先会调用Event对象的preventDefault()和cancelBubble()方法。这意味着通过triggerHandler()手动触发的事件不会冒泡,也不会执行相关联的默认操作。
19.4.7 自定义事件
jQuery的事件管理体系是为标准事件设计的,比如Web浏览器产生的鼠标单击和按键按下。但是,jQuery不限于这些事件,你可以使用任何想用的字符串来作为事件类型名称。使用bind()可以注册这种“自定义事件”的处理程序,使用trigger()可以调用这些处理程序。
对于书写模块化代码,实现发布/订阅模型或观察者模式时,这种自定义事件处理程序的间接调用被证明是非常有用的。使用自定义事件时,通常你会发现,使用jQuery.event.trigger()函数替代trigger()方法,来全局触发处理器会更有用:
//用户单击"logoff"按钮时,广播一个自定义事件
//给任何需要保存状态的感兴趣的观察者,然后
//导航到logoff页面
$("#logoff").click(function(){
$.event.trigger("logoff");//广播一个事件
window.location="logoff.php";//导航到新页面
});
在19.6.4节我们将看到jQuery的Ajax方法会像上面这样广播自定义事件,以通知感兴趣的监听器。
19.4.8 实时事件
bind()方法绑定事件处理程序到指定文档元素,就与addEventListner()和attachEvent()(参见第17章)一样。但是,使用jQuery的Web应用经常动态创建新元素。如果使用bind()给文档中的所有<a>元素绑定了事件处理程序,接着又创建了带有<a>元素的新文档内容,这些新元素和老元素不会拥有相同的事件处理程序,其行为将不一样。
jQuery使用“实时事件”来解决这一问题。要使用实时事件,需要使用delegate()和undelegate()方法来替代bind()和unbind()。通常,在$(document)上调用delegate(),并传入一个jQuery选择器字符串、一个jQuery事件类型字符串以及一个jQuery事件处理程序函数。它会在document或window上(或jQuery对象中的任何元素上)注册一个内部处理程序。当指定类型的事件冒泡到该内部处理程序时,它会判断事件目标(该事件所发生在的元素)是否匹配选择器字符串。如果匹配,则调用指定的处理程序函数。因此,为了同时处理老的和新创建的<a>元素上的mouseover事件,可能需要像下面这样注册处理程序:
$(document).delegate("a","mouseover",linkHandler);
否则,需要使用bind()来处理文档中的静态部分,然后使用delegate()来处理动态修改的部分:
//静态链接的静态事件处理程序
$("a").bind("mouseover",linkHandler);//文档中动态更新的部分使用实时事件处理程序
$(".dynamic").delegate("a","mouseover",linkHandler);
与bind()方法拥有三参数版本来指定事件对象的data属性一样,delegate()方法拥有4参数版本用来干同样的事。使用这种版本时,将数据值作为第三参数传入,处理程序函数则作为第4参数。
理解这点很重要:实时事件依赖于事件冒泡。当事件冒泡到document对象时,它有可能已经传递给了很多静态事件处理程序。如果这些处理程序中有任何一个调用了Event对象的cancelBubble()方法,实时事件处理程序将永远不会调用。
jQuery定义了一个名为live()的方法,也可以用来注册实时事件。live()比delegate()更难理解一点,但与bind()一样,live()也有两参数和三参数的调用形式,并且在实际中用得更普遍。上面对delegate()的两个调用,也可以使用live()来写,如下所示:
$("a").live("mouseover",linkHandler);
$("a",$(".dynamic")).live("mouseover",linkHandler);
在jQuery对象上调用live()方法时,该对象中的元素实际上并没有使用。真正有关系的是用来创建jQuery对象的选择器字符串和上下文对象(传递给$()的第一个和第二个参数)。jQuery对象通过context和selector属性来使得这些值可用(参见19.1.2节)。通常,仅带一个参数调用$()时,context是当前文档。因此,对于jQuery对象x,下面两行代码做的事情是一样的:
x.live(type,handler);
$(x.context).delegate(x.selector,type,handler);
要注销实时事件处理程序,使用die()或undelegate()。可以带一个或两个参数调用die()。带有一个事件类型参数时,die()会移除匹配选择器和事件类型的所有实时事件处理程序。带有事件类型和处理程序函数参数时,它只会移除掉指定的处理程序。一些例子:
$('a').die('mouseover');//移除<a>元素上mouseover事件的所有实时处理程序
$('a').die('mouseover',linkHandler);//只移除一个指定的实时处理程序
undelegate()类似die(),但更显式地分开context(内部事件处理程序所注册的元素)和选择器字符串。上面对die()的调用可以写成下面这样:
$(document).undelegate('a');//移除<a>元素上的所有实时处理程序
$(document).undelegate('a','mouseover');//移除mouseover的实时处理程序
$(document).undelegate('a','mouseover',linkHandler);//移除指定处理程序
最后,undelegate()也不带任何参数调用。在这种情况下,它会注销从选中元素委托的所有实时事件处理程序。
19.5 动画效果
第16章展示了如何通过脚本来修改文档元素的CSS样式。例如,通过设置CSS的visibility属性,可以显示和隐藏元素。16.3.1节进一步演示了如何通过脚本控制CSS来产生动画视觉效果。例如,除了仅让一个元素消失,还可以在半秒的时间内逐步减少opacity属性的值,使其快速淡出,而不是瞬间消失。这些动画视觉效果能给用户带来更愉悦的体验,jQuery使其实现起来更简单。
jQuery定义了fadeIn()和fadeOut()等简单方法来实现常见视觉效果。除了简单动画方法,jQuery还定义了一个animate()方法,用来实现更复杂的自定义动画。下面将讲述这些简单动画方法,以及更通用的animate()方法。首先,让我们了解下jQuery动画框架的一些通用特性。
每段动画都有时长,用来指定动画效果持续多长时间。可以使用毫秒数值或字符串来指定时长。字符串"fast"表示200ms。字符串"slow"表示600ms。如果指定的字符串时长jQuery无法识别,则采用默认时长400ms。可以给jQuery.fx.speeds添加新的字符串到数值映射关系来定义新的时长名字:
jQuery.fx.speeds["medium-fast"]=300;
jQuery.fx.speeds["medium-slow"]=500;
jQuery动画方法经常使用动画时长来作为可选的第一个参数。如果省略时长参数,通常会得到默认值400ms。注意,省略时长时,有部分方法会立刻跳到最后一帧,没有中间的动画效果:
$("#message").fadeIn();//用淡入效果显示元素,持续400ms
$("#message").fadeOut("fast");//用淡出效果隐藏元素,持续200ms
禁用动画
在很多网站上,动画视觉效果已经成为标配,但是,并不是所有用户都喜欢:有些用户觉得动画分散注意力,有些则感觉动画导致操作不便。残障用户可能会发现动画会妨碍屏幕阅读器等辅助软件正常工作,老旧硬件上的用户则会感觉动画会耗费很多CPU时间。为了对用户保持尊重,我们通常应该让动画简单朴素,并提供一个选项可以彻底禁用动画。使用jQuery可以非常简单地全局禁用所有动画:简单地设置jQuery.fx.off为true就好。该设置会将每段动画的时长都变成0ms,这样动画看起来就像是没有动画效果的立刻切换了。
为了让最终用户可以禁用动画,可以在脚本上使用如下代码:
$(".stopmoving").click(function(){jQuery.fx.off=true;});
这样,当网页设计者在页面中加入带有"stopmoving"类的元素时,用户就可以单击该元素来禁用动画。
jQuery动画是异步的。调用fadeIn()等动画方法时,它会立刻返回,动画则在“后台”执行。由于动画方法会在动画完成之前返回,因此可以向很多jQuery动画方法传入第二个参数(也是可选的),该参数是一个函数,会在动画完成时调用。该函数在调用时不会有任何参数传入,但this值会设置为发生动画的文档元素。对于每个选中元素都会调用一次该回调函数:
//用淡入效果快速显示元素,动画完成时,在元素里显示一些文字
$("#message").fadeIn("fast",function(){$(this).text("Hello World");});
给动画方法传入回调函数,可以在动画结束时执行操作。不过,如果只是想顺序执行多段动画的话,回调方式是没有必要的。jQuery动画默认是队列化的(19.5.2节下面的“2.动画选项对象”节会讲述如何覆盖默认方式)。如果一个元素已经在动画过程中,再调用一个动画方法时,新动画不会立刻执行,而会延迟到当前动画结束后才执行。例如,可以让一个元素在持久显示前,先闪烁一阵:
$("#blinker").fadeIn(100).fadeOut(100).fadeIn(100).fadeOut(100).fadeIn();
jQuery动画方法可以接受可选的时长和回调参数。还可以传入一个对象来调用动画方法,该对象的属性指定动画选项:
//将时长和回调参数作为对象属性而不是参数传入
$("#message").fadeIn({
duration:"fast",
complete:function(){$(this).text("Hello World");}
});
使用通用的animate()方法时,经常传入选项对象作为参数,其实,这也可以用于更简单的动画方法。使用选项对象可以设置高级选项,比如控制动画的队列和缓动。19.5.2节下面的“2.动画选项对象”节会讲述可用的选项。
19.5.1 简单动画
jQuery定义了9个简单的动画方法用来隐藏和显示元素。根据实现的动画类型,它们可以分为三组:
fadeIn()、fadeOut()、fadeTo()
这是最简单的动画:fadeIn()和fadeOut()简单地改变CSS的opacity属性来显示或隐藏元素。两者都接受可选的时长和回调参数。fadeTo()稍有不同:它需要传入一个opacity目标值,fadeTo()会将元素的当前opacity值变化到目标值。调用fadeTo()方法时,第一参数必须是时长(或选项对象),第二参数是opacity目标值,回调函数则是可选的第三个参数。
show()、hide()、toggle()
上面的fadeOut()方法可以让元素不可见,但依旧保留了元素在文档布局中的占位。hide()方法则会将元素从布局中移除,就好像把CSS的display属性设置为none一样。当不带参数调用时,hide()和show()方法只是简单地立刻隐藏或显示选中元素。带有时长(或选项对象)参数时,它们会让隐藏或显示有个动画过程。hide()在将元素的opacity减少到0时,同时它还会将元素的宽度和高度收缩到0.show()则进行反向操作。
toggle()可以改变在上面调用它的元素的可视状态:如果隐藏,则调用show();如果显示,则调用hide()。与show()和hide()一样,必须传入时长或选项对象给toggle()来产生动画效果。给toggle()传入true和不带参数调用show()是一样的,传入false则和不带参数调用hide()是一样的。注意,如果传入两个或多个函数参数给toggle(),它会注册为事件处理程序,这在19.4.1节讲述过。
slideDown()、slideUp()、slideToggle()
slideUp()会隐藏jQuery对象中的元素,方式是将其高度动态变化到0,然后设置CSS的display属性为"none"。slideDown()执行反向操作,来使得隐藏的元素再次可见。slideToggle()使用向上滑动或向下滑动动画来切换元素的可见性。这三个方法都接受可选的时长和回调参数(或选项对象参数)。
下面是一个例子,它调用了该组方法中的每一个。要记得jQuery动画默认情况下是队列化的,因此这些动画会一个接一个执行:
//用淡出效果将所有图像隐藏,然后显示它们,接着向上滑动,再向下滑动
$("img").fadeOut().show(300).slideUp().slideToggle();
各种jQuery插件(参见19.9节)会添加额外的动画方法到jQuery类库中。jQuery UI类库(参见19.10节)拥有一套特别全面的动画效果。
19.5.2 自定义动画
与简单动画方法实现的效果相比,使用animate()方法可以实现更多通用动画效果。传给animate()方法的第一个参数指定动画内容,剩余参数指定如何定制动画。第一个参数是必需的:它必须是一个对象,该对象的属性指定要变化的CSS属性和它们的目标值。animate()方法会将每个元素的这些CSS属性从初始值变化到指定的目标值。例如,上面描述的slideUp()效果可以用以下代码来实现:
//将所有图片的高度缩小到0
$("img").animate({height:0});
第二个参数是可选的,可以传入一个选项对象给animate()方法:
$("#sprite").animate({
opacity:.25,//将不透明度调整为0.25
font-size:10//将字体大小变化到10像素
},{
?duration:500,//动画持续半秒
complete:function(){//在动画完成时调用该函数
this.text("Goodbye");//改变元素的文本
}
});
除了将选项对象作为第二个参数传入,animate()方法还允许将三个最常用的选项作为参数传入。可以将动画时长(数值或字符串)作为第二个参数传入。可以指定缓动函数名为第三个参数。(很快会讲解缓动函数。)最后可以将回调函数指定为第四个参数。
通常,animate()方法接受两个对象参数。第一个指定动画内容,第二个指定如何定制动画。要彻底理解如何使用jQuery来实现动画,还需要弄明白这两个对象的更多细节。
1.动画属性对象
animate()方法的第一个参数必须是对象。该对象的属性名必须是CSS属性名,这些属性的值必须是动画的目标值。动画只支持数值属性:对于颜色、字体或display等枚举属性是无法实现动画效果的[6]。如果属性值是数值,则默认单位是像素。如果属性值是字符串,可以指定单位。如果省略单位,则默认依旧是像素。还可以指定相对值,用“+=”前缀表示增加,或用“-=”表示减少。例如:
$("p").animate({
"margin-left":"+=.5in",//增加段落缩进
opacity:"-=.1"//同时减少不透明度
});
注意上面的对象字面量中属性名"margin-left"两旁引号的使用。属性名中的连字符表示这不是一个合法的JavaScript标识符,所以它必须用引号括起来。当然,jQuery也允许使用marginLeft这种大小写混合的写法。
除了数值(可以带有单位、“+=”或“-=”前缀),在jQuery动画属性对象中,还可以使用三个其他值。"hide"值会保存属性的当前值,然后将该属性的值变化到0。"show"值会将CSS属性的值还原到之前保存的值。如果一段动画使用了"show",jQuery会在动画完成时调用show()方法。如果一段动画使用了"hide",jQuery会在动画完成时调用hide()方法。
还可以使用"toggle"来实现显示或隐藏,具体效果取决于该属性的当前设置。可以用下面的代码实现"slideRight"效果(和slideUp()方法类似,只是动画内容是元素宽度):
$("img").animate({
width:"hide",
borderLeft:"hide",
borderRight:"hide",
paddingLeft:"hide",
paddingRight:"hide"
});
将上面的属性值替换为"show"或"toggle",就可以产生水平滑动的伸缩效果,类似slideDown()和slideToggle()效果。
2.动画选项对象
animate()方法的第二个参数是可选的,该选项对象用来指定动画如何执行。有两个最重要的选项我们已经接触过。duration属性指定动画持续的毫秒时间,该属性的值还可以是"fast"、"slow"或任何在jQuery.fx.speeds中定义的名称。
另一个接触过的选项是complete属性:它指明在动画完成时的回调函数。和complete属性类似,step属性指定在动画每一步或每一帧调用的回调函数。在回调函数中,this指向正在连续变化的元素,第一个参数则是正在变化的属性的当前值。
在选项对象中,queue属性指定动画是否需要队列化——是否需要等到所有尚未发生的动画都完成后再执行该动画。默认情况下,所有动画都是队列化的。将queue属性设置为false可以取消队列化。非队列化的动画会立刻执行。随后队列化的动画不会等待非队列化的动画执行完成后才执行。考虑以下代码:
$("img").fadeIn(500)
.animate({"width":"+=100"},{queue:false,duration:1000})
.fadeOut(500);
上面的fadeIn()和fadeOut()效果是队列化的,但animate()的调用(在1000毫秒内连续改变width属性)是非队列化的。这段width动画和fadeIn()效果的开始时间相同。fadeOut()效果会在fadeIn()效果完成时立刻开始,它不会等到width动画完成。
缓动函数
实现动画时,时间和动画属性值之间可以是线性关系,这种方式很直接,但不够好。例如,一段时长400ms的动画,在100ms时,动画完成了25%。在该线性动画中,如果将不透明度从1.0变化到0.0(可能是一个fadeOut()调用),则在100ms时,不透明度应该是0.75。然而,事实表明,非线性的动画效果会带来更愉悦的体验。因此,jQuery引入了“缓动函数”,来将基于时间的完成百分比映射到动画效果的百分比。jQuery在调用缓动函数时会传入一个基于时间的0~1之间的值。缓动函数会返回另一个0~1之间的值,jQuery会根据该返回值来计算CSS属性的值。通常,缓动函数在传入0时会返回0,在传入1时会返回1。当然,在0~1之间缓动函数可以是非线性的,这可以让动画有加速和减速效果。
jQuery的默认缓动函数是正弦函数:它开始很慢,接着加速,然后再缓慢“缓动”变化到终值。jQuery中的缓动函数有名字。默认的缓动函数名为"swing",jQuery还实现了一个线性缓动函数,名字为"linear"。可以添加自定义缓动函数到jQuery.easing对象上:
jQuery.easing["squareroot"]=Math.sqrt;
在jQuery UI类库中,有一个“jQuery缓动插件”定义了一套更复杂的缓动函数。
剩余的动画选项和缓动函数有关。选项对象的easing属性指定缓动函数名。jQuery默认使用的是命名为"swing"的正弦函数。如果想让动画线性变化,可以使用如下选项:
$("img").animate({"width":"+=100"},{duration:500,easing:"linear"});
是否还记得,除了传入选项对象,duration、easing和complete选项可以指定为animate()方法的参数。因此,上面的代码还可以写成:
$("img").animate({"width":"+=100"},500,"linear");
最后,jQuery动画框架甚至还允许为不同的CSS动画属性指定不同的缓动函数。这有两种方式来实现,代码示例如下:
//用hide()方法隐藏图片,图片的大小采用线性动画
//不透明度则使用默认的"swing"缓动函数
//实现方式一:
//使用specialEasing选项来指定自定义缓动函数
$("img").animate({width:"hide",height:"hide",opacity:"hide"},?
{specialEasing:{width:"linear",height:"linear"}});//实现方式二:
//在第一个对象参数中传入[目标值,缓动函数]数组
$("img").animate({
width:["hide","linear"],height:["hide","linear"],opacity:"hide"
});
19.5.3 动画的取消、延迟和队列
jQuery还定义了一些动画和队列相关的方法,我们需要进一步了解。首先是stop()方法:它用来停止选中元素上的当前正在执行的任何动画。top()方法接受两个可选的布尔值参数。如果第一个参数是true,会清除该选中元素上的动画队列:除了停止当前动画,还会取消任何等待执行的动画。第一个参数的默认值是false:如果忽略该参数,等待执行的动画不会被取消。第二个参数用来指定正在连续变化的CSS属性是否保留当前值,还是应该变化到最终目标值。传入true可以让它们变化到最终值。传入false(或省略该参数)会让它们保持为当前值。
当动画是由用户事件触发时,在开始新的动画前,可能需要取消掉当前或等待执行的任何动画。比如:
//当鼠标悬浮在图片上时,图片变得不透明
//注意:我们没有在鼠标事件上持有队列化动画
$("img").bind({
mouseover:function(){$(this).stop().fadeTo(300,1.0);},
mouseout:function(){$(this).stop().fadeTo(300,0.5);}
});
与动画相关的第二个方法是delay()。这会直接添加一个时间延迟到动画队列中:第一个参数是时长(以毫秒为单位的数值或字符串),第二个参数是队列名,是可选的(通常并不需要第二个参数:接下来我们会解释队列名)。可以在复合动画中使用delay(),代码如下:
//快速淡出为半透明,等一等,然后向上滑动
$("img").fadeTo(100,0.5).delay(200).slideUp();
在上面的stop()方法例子中,使用mouseover和mouseout事件来变化图片的透明度。可以调整该例子:在开始动画时,添加一个短小的延迟。这样,当鼠标快速滑过图片而不停留时,不会有任何分神的动画产生:
$("img").bind({
mouseover:function(){$(this).stop(true).delay(100).fadeTo(300,1.0);},
mouseout:function(){$(this).stop(true).fadeTo(300,0.5);}
});
和动画相关的最后一组方法可以对jQuery的队列机制进行底层操作。jQuery队列是按顺序执行的函数列表。每一个队列都与一个文档元素(或者是Document或Window对象)关联,每一个元素的队列都与其他元素的队列彼此独立。可以使用queque()方法给队列添加一个新函数。当某个函数到达队列头部时,它会自动从队列中去除并被调用。当函数被调用时,this指向与队列相关联的元素。被调用的函数会传入唯一一个回调函数作为参数。当函数完成运行时,它必须调用回调函数。这可以运行队列中的下一个操作,如果不调用回调函数,该队列会停止运行,剩余的函数将永远不会被调用。
我们知道,通过给jQuery动画方法传入回调函数,就可以在动画完成后执行一些操作。通过对函数执行队列操作,也可达到这一目的:
//淡入显示一个元素,稍等片刻,设置一些文字,然后变化边框
$("#message").fadeIn().delay(200).queue(function(next){
$(this).text("Hello World");//显示一些文字
next();//运行队列中的下一项
}).animate({borderWidth:"+=10px;"});//将边框变粗
队列函数中的回调函数参数是jQuery 1.4引入的新特性。对于jQuery类库之前的版本,需要调用dequeue()方法“手动”取消队列中的下一个函数:
$(this).dequeue();//替代next()方法
如果在队列中什么也没有,调用dequeue()方法不会有任何响应。反之,它则会将队列头部的函数从队列中移除,并调用它,设置的this值和传入的回调函数如上所述。
还有一些笨拙的方式来操作队列。clearQueue()方法用来清除队列。给queue()方法传入一个函数组成的数组而不是单一函数时,会用传入的函数数组来替换当前队列。如果在调用queue()方法时,不传入任何参数,则会返回当前队列数组。jQuery还将queue()和dequeue()定义成了工具函数。如果想给元素e的队列添加一个函数f,可以使用以下方法或函数:
$(e).queue(f);//创建一个持有e的jQuery对象,并调用queue()方法
jQuery.queue(e,f);//直接调用jQuery.queue()工具函数
最后,留意下queue()、dequeue()和clearQueue()方法都可以有一个可选的队列名来作为第一个参数。jQuery动画方法使用的队列名是"fx",这是没有指定队列名时默认使用的队列。当想要顺序执行异步操作时,jQuery队列机制非常有用:原来需要给每一个异步操作传入回调函数来触发队列中的下一个函数,现在可以直接使用jQuery队列来管理异步序列。只须传入非"fx"的队列名,并记得队列中的函数不会自动执行。必须显式调用dequeue()方法来运行第一个函数,然后每一步操作在完成时必须把下一个操作从队列中移出。
19.6 jQuery中的Ajax
在Web应用编程技术里,Ajax很流行,它使用HTTP脚本(参考第18章)来按需加载数据,而不需要刷新整个页面。在现代Web应用中,Ajax技术非常有用,因此jQuery内置了Ajax工具来简化使用。jQuery定义了一个高级工具方法和四个高级工具函数。这些高级工具都基于同一个强大的底层函数:jQuery.ajax()。下面的章节会首先描述这些高级工具,然后再详细阐述jQuery.ajax()函数。为了彻底理解高级工具的使用,我们需要理解jQuery.ajax(),即便可能永远不用显式使用它。
19.6.1 load()方法
load()是所有jQuery工具中最简单的:向它传入一个URL,它会异步加载该URL的内容,然后将内容插入每一个选中元素中,替换掉已经存在的任何内容。例如:
//每隔60秒加载并显示最新的状态报告
setInterval(function(){$('#status').load("status_report.html");},60000);
19.4.1节也讲到了load()方法,它用来注册load事件的处理程序。如果传给该方法的第一个参数是函数而不是字符串,则load()方法是事件处理程序注册方法而不是Ajax方法。如果只想显示被加载文档的一部分,可以在URL后面添加一个空格和一个jQuery选择器。当URL加载完成后,jQuery会用指定的选择器来从加载好的HTML中选取需要显示的部分:
//加载并显示天气预告的温度部分
$('#temp').load("wheather_report.html#temperature");
注意:URL后面的选择器看起来很像片断标识符(14.2节讲述的URL的hash部分)。不同的是,如果想只插入被加载文档的选中部分的话,则空格是必需的。
除了必须的URL参数,load()方法还接受两个可选参数。第一个可选参数表示的数据,可以追加到URL后面,或者与请求一起发送。如果传入的是字符串,则会追加到URL后面(放在“?”或“&”后面)。如果传入对象,该对象会被转化为一个用“&”分隔的名/值对后与请求一起发送。(对象转化为字符串的具体细节在19.6.2节下面的"2.jQuery.getJSON()"节中描述。)通常情况下,load()方法发送HTTP GET请求,但是如果传入数据对象,则它会发送POST请求。下面是两个例子:
//加载特定区号的天气预报
$('#temp').load("us_weather_report.html","zipcode=02134");//使用对象作为数据,并指定为华氏温度
$('#temp').load("us_weather_report.html",{zipcode:02134,units:'F'});
load()方法的另一个可选参数是回调函数。当Ajax请求成功或未成功,以及(当请求成功时)URL加载完毕并插入选中元素时,会调用该回调函数。如果没有指定任何数据,回调函数可以作为第二个参数传入。否则,它必须是第三个参数。在jQuery对象的每一个元素上都会调用回调函数,并且每次调用都会传入三个参数:被加载URL的完整文本内容、状态码字符串,以及用来加载该URL的XMLHttpRequest对象。其中,状态参数是jQuery的状态码,不是HTTP的状态码,其值是类似"success"、"error"和"timeout"的字符串。
jQuery的Ajax状态码
jQuery的所有Ajax工具,包括load()方法,会调用回调函数来提供请求成功或失败的异步消息。这些回调函数的第二个参数是一个字符串,可以取以下值:
"success"
表示请求成功完成。
"notmodified"
该状态码表示请求已正常完成,但服务器返回的响应内容是HTTP 304"Not Modified",表示请求的URL内容和上次请求的相同,没有变化。只有在选项中设置ifModified为true时,该状态码才会出现(参考19.6.3节下面的“1.通用选项”节)。jQuery 1.4认为"notmodified"状态码是成功的,但之前的版本会将其当成错误。
"error"
表示请求没有成功完成,原因是某些HTTP错误。更多细节,可以检查传入每一个回调函数中的XMLHttpRequest对象的HTTP状态码来获取。
"timout"
如果Ajax请求没有在选定的超时区间内完成,会调用错误回调,并传入该状态码。默认情况下,jQuery的Ajax请求没有超时限定,只有指定了timeout选项(见19.6.3节下面的“1.通用选项”节)时才能看到该状态码。
"parsererror"
该状态码表示HTTP请求已成功完成,但jQuery无法按照期望的方式解析。例如,如果服务器返回的是不符合格式的XML文档或不符合格式的JSON文本时,就会出现该状态码。注意拼写:是"parsererror",而不是"parseerror"。
19.6.2 Ajax工具函数
jQuery的其他Ajax高级工具不是方法,而是函数,可以通过jQuery或$直接调用,而不是在jQuery对象上调用。jQuery.getScript()加载并执行JavaScript代码文件。jQuery.getJSON()加载URL,将其解析为JSON,并将解析结果传递到指定的回调函数中。这两个函数都会调用一个更通用的URL获取函数:jQuery.get()。最后,jQuery.post()和jQuery.get()很类似,除了执行的是HTTP POST而不是GET请求。与load()方法一样,所有这些函数都是异步的:在任何数据加载前它们就会返回调用者,加载结果则通过调用指定的回调函数来通知。
1.jQuery.getScript()
jQuery.getScript()函数的第一个参数是JavaScript代码文件的URL。它会异步加载文件,加载完成后在全局作用域执行该代码。它能同时适用于同源和跨源脚本:
//从其他服务器动态加载脚本
jQuery.getScript("http://example.com/js/widget.js");
可以传入回调函数作为第二个参数,在这种情况下,jQuery会在代码加载和执行完成后调用一次该回调函数:
//加载一个类库,并在加载完成时立刻使用它
jQuery.getScript("js/jquery.my_plugin.js",function(){
$('div').my_plugin();//使用加载的类库
});
jQuery.getScript()通常会使用XMLHttpRequest对象来获取要执行的脚本内容。但对于跨域请求(脚本存放在与当前文档的不一样的服务器上),jQuery会使用<script>元素来加载脚本(参考18.2节)。在同源情况下,回调函数的第一个参数是脚本的文本内容,第二个参数是"success"状态码,第三个参数则是用来获取脚本内容的XMLHttpRequest对象。在同源情况下,jQuery.getScript()函数的返回值也是该XMLHttpRequest对象。对于跨源请求,不存在XMLHttpRequest对象,并且脚本的内容获取不到。在这种情况下,回调函数的第一个和第三个参数是undefined,jQuery.getScript()的返回值也是undefined。
传递给jQuery.getScript()的回调函数,仅在请求成功完成时才会被调用。如果需要在发生错误以及成功时都得到通知,则需要使用底层的jQuery.ajax()函数。该节描述的其他三个工具函数也是如此。
2.jQuery.getJSON()
jQuery.getJSON()与jQuery.getScript类似:它会获取文本,然后特殊处理一下,再调用指定的回调函数。jQuery.getJSON()获取到文本后,不会将其当做脚本执行,而会将其解析为JSON(使用19.7节描述的jQuery.parseJSON()函数)。jQuery.getJSON()只有在传入了回调参数时才有用。当成功加载URL,以及将内容成功解析为JSON后,解析结果会作为第一个参数传入回调函数中。与jQuery.getScript()一样,回调函数的第二个和第三个参数是"success"状态码和XMLHttpRequest对象:
//假设data.json包含文本:'{"x":1,"y":2}'
jQuery.getJSON("data.json",function(data){//data参数是对象{x:1,y:2}
});
与jQuery.getScript()不同,jQuery.getJSON()接受一个可选的数据对象参数,就和传入load()方法中的一样。如果传入数据到jQuery.getJSON()中,该数据必须是第二个参数,回调函数则是第三个。如果不传入任何数据,则回调函数可以是第二个参数。如果数据是字符串,则它会被添加到URL的“?”或“&”后面。如果数据是一个对象,则它会转化为字符串(参见下面方框中的内容),然后添加到URL上。
传递数据给jQuery的Ajax工具
jQuery的大多数Ajax方法都接受一个参数(或选项)用来指定与URL一起发送给服务器的数据。通常,该数据的形式是URL编码的、用“&”分隔的名/值对。(这个数据格式就是已知的"application/x-www-form-urlencoded"MIME类型。这类似于JSON格式:一种将JavaScript简单对象与字符串互相转化的格式。)对于HTTP GET请求,该数据字符串会添加到请求URL后面。对于POST请求,则在所有发送的HTTP请求头后面,当做请求的内容体发送它。
获取该格式的数据字符串的一种方式是,调用包含表单或表单元素的jQuery对象的serialize()方法。例如,可以使用如下代码来调用load()方法提交HTML表单:
$('#submit_button').click(function(event){
$(this.form).load(//通过加载新内容来替换表单
this.form.action,//表单url
$(this.form).serialize());//将表单数据附加到表单url后面
event.preventDefault();//取消掉表单的默认提交
this.disabled="disabled";//防止多次提交
});
如果将jQuery Ajax函数的数据参数(或选项)设置为对象而不是字符串,jQuery通常会调用jQuery.param()来将对象转化成字符串(除了下面提到的一个异常)。该工具函数会将对象的属性当成名/值对,例如,会将对象{x:1,y:"hello"}转换成字符串"x=1&y=hello"。
在jQuery 1.4中,jQuery.param()能处理更复杂的JavaScript对象。如果对象的某个属性值是数组,该数组中的每一项都会在结果字符串中拥有自己的一个名/值对,并且属性名后会添加方括号。如果对象的某个属性值是对象,则内嵌对象的属性名会放置在方括号里并添加到外层属性名中。例如:
$.param({a:[1,2,3]})//返回"a[]=1&a[]=2&a[]=3"
$.param({o:{x:1,y:true}})//返回"o[x]=1&o[y]=true"
$.param({o:{x:{y:[1,2]}}})//返回"o[x][y][]=1&o[x][y][]=2"
为了后向兼容jQuery 1.3及其之前的版本,可以传递true给jQuery.param()的第二个参数,或设置traditional选项为true。这可以阻止对值为数组或对象的属性进行进一步序列化。
偶尔,需要将Document(或一些其他不需要自动转换的对象)作为POST请求的内容体传递。在这种情况下,可以设置contentType选项来指定数据类型,并将processData选项设置为false,以阻止jQuery将数据对象传递给jQuery.param()。
如果传递给jQuery.getJSON()的URL或数据字符串在末尾或“&”字符前含有“=?”字符串,则表明这是一个JSONP请求。(参考18.2节中JSONP的解释。)jQuery会创建一个回调函数,并用该回调函数的函数名替换掉“=?”中的“?”号,接着jQuery.getJSON()的行为就会像请求脚本文件一样,而不是JSON对象。这对静态JSON数据文件无效,它只能与支持JSONP的服务器脚本一起才能工作。由于JSONP被当做脚本来处理,因此这意味着JSON格式的数据可以跨域请求。
3.jQuery.get()和jQuery.post()
jQuery.get()和jQuery.post()获取指定URL的内容,如果有数据的话,还可传入指定数据,最后则将结果传递给指定的回调函数。jQuery.get()使用HTTP GET请求来实现,jQuery.post()使用HTTP POST请求,其他两者则都是一样的。与jQuery.getJSON()一样,这两个方法也接受相同的三个参数:必需的URL,可选的数据字符串或对象,以及一个技术上可选但实际上总会使用的回调函数。调用的回调函数会被传入三个参数:第一个参数是返回的数据;第二个是"success"字符串;第三个则是XMLHttpRequest对象(如果有的话):
//从服务器请求文本并在警告对话框中显示
jQuery.get("debug.txt",alert);
除了上面描述的三个参数,还有两个方法接受可选的第4个参数(如果省略数据参数的话,则作为第三个参数传入),该参数指定被请求数据的类型。第4个参数会影响在传入回调函数前数据的处理。load()方法使用"html"类型,jQuery.getScript()使用"script"类型,jQuery.getJSON()则使用"json"类型。与上面这些专用函数相比,jQuery.get()和jQuery.post()更灵活。该参数的有效值,以及省略该参数时jQuery的行为,在下面描述。
jQuery的Ajax数据类型
可以给jQuery.get()或jQuery.post()传递下面6种类型作为参数。此外,下面会讲到,使用dataType选项也可以传递这些类型给jQuery.ajax()方法:
"text"
将服务器的响应作为纯文本返回,不做任何处理。
"html"
该类型和"text"一样:响应是纯文本。load()方法使用该类型,将返回的文本插入到文档自身中。
"xml"
请求的URL被认为指向XML格式的数据,jQuery使用XMLHttpRequest对象的responseXML属性来替代responseText属性。传给回调函数的值是一个表示该XML文档的Document对象,而不是保存文档文本的字符串。
"script"
请求的URL被认为指向JavaScript文件,返回的文本在传入回调函数前,会当做脚本执行。jQuery.getScript()使用该类型。当类型是"script"时,jQuery可以使用<script>元素来替代XMLHttpRequest对象,因此可以处理跨域请求。
"json"
请求的URL被认为指向JSON格式的数据文件。会使用jQuery.parseJSON()(参考19.7节)来解析返回的内容,得到JSON对象后传入回调函数。jQuery.getJSON()使用该类型。如果类型是"json"同时URL或数据字符串含有"=?",该类型会转换成"jsonp"。
"jsonp"
请求的URL被认为指向服务器脚本,该脚本支持JSONP协议,可以将JSON格式的数据作为参数传递给客户端指定的函数。(JSONP的更多细节请参考18.2节。)在该类型下,传递给回调函数的是解析好的对象。由于JSONP请求可以通过<script>元素来实现,因此该类型可以用来做跨域请求,就和"script"类型一样。使用该类型时,URL或数据字符串经常会包含一个类似"&jsonp=?"或"&callback=?"的参数。jQuery会将参数中的“?”替换为自动产生的回调函数名。(可以参考19.6.3节下面的“3.不常用的选项和钩子”节中的jsonp和jsonpCallback选项来做替代。)
如果调用jQuery.get()、jQuery.post()或jQuery.ajax()函数时没有指定以上类型中的任何一个,jQuery会检查HTTP响应中的Content-Type头。如果该头部信息包含"xml"字符串,则传入回调函数中的是XML文档。否则,如果头部包含"json"字符串,则数据被被解析成JSON并把解析后的对象传给回调函数。否则,如果头部含有"javascript"字符串,则数据被当做脚本执行。如果以上都不符合,则数据会被当做纯文本处理。
19.6.3 jQuery.ajax()函数
jQuery的所有Ajax工具最后都会调用jQuery.ajax()——这是整个类库中最复杂的函数。jQuery.ajax()仅接受一个参数:一个选项对象,该对象的属性指定Ajax请求如何执行的很多细节。例如,jQuery.getScript(url,callback)与以下jQuery.ajax()的调用等价:
jQuery.ajax({
type:"GET",//HTTP请求方法
url:url,//要获取数据的url
data:null,//不给url添加任何数据
dataType:"script",//一旦获取到数据,立刻当做脚本执行
success:callback//完成时调用该函数
});
jQuery.get()和jQuery.post()也接受上面这5个基本选项。然而,如果直接调用jQuery.ajax()的话,它可以支持更多其他选项。下面会解释所有选项(包含上面这5个基本选项)。
在深入了解所有选项之前,我们得知道可以通过给jQuery.ajaxSetup()传入一个选项对象来设置任意选项的默认值:
jQuery.ajaxSetup({
timeout:2000,//在两秒后取消所有Ajax请求
cache:false//通过给URL添加时间戳来禁用浏览器缓存
});
运行以上代码后,指定的timeout和cache选项会在所有未指定这两个选项的值的Ajax请求中使用(包括jQuery.get()和load()方法等高级工具)。
在阅读下面章节中jQuery的大量选项和回调函数时,参考19.6.1节和19.6.2节下面的“3.jQuery.get()和jQuery.post()”节中关于jQuery Ajax状态码和数据类型字符串的内容会非常有裨益。
jQuery 1.5中的Ajax
在本书即将交付印刷时,jQuery 1.5发布了。在1.5版本中,重写了Ajax模块,提供一些非常便捷的新特性。最重要的一个特性是jQuery.ajax()和所有之前描述的Ajax工具函数现在都返回一个jqXHR对象。该对象模拟XMLHttpRequest的API,甚至对于那些没有使用XMLHttpRequest对象的请求(比如$.getScript()发起的请求)也进行了模拟。更进一步的是,jqXHR对象定义了success()和error()方法,可用来注册请求成功或失败时的回调函数。例如,不用给jQuery.get()传递回调函数,只须将回调函数传递给工具函数返回的jqXHR对象的success()方法:
jQuery.get("data.txt")
.success(function(data){console.log("Got",data);})
.success(function(data){process(data);});
1.通用选项
jQuery.ajax()中最常用的选项如下:
type
指定HTTP的请求方法。默认是"GET"。另一个常用值是"POST"。可以指定其他HTTP的请求方法,比如"DELETE"或"PUSH",但不是所有浏览器都支持它们。注意:该选项的命名有误导嫌疑:该选项与请求或响应的数据类型没有任何关系,或许取名为"method"或是一个更好的选择。
url
要获取的URL。对于GET请求,data选项会添加到该URL后。对于JSONP请求,当cache选项为false时,jQuery可以添加参数到URL中。
data
添加到URL中(对GET请求)或在请求的内容体中(对POST请求)发送的数据。这可以是字符串或对象。通常会把对象转化为字符串,就如19.6.2节下面的"2.jQuery.getJSON()"节中描述的一样,除了在process data选项中描述的异常情况。
dataType
指定响应数据的预期类型,以及jQuery处理该数据的方式。合法值是"text"、"html"、"script"、"json"、"jsonp"和"xml"。19.6.2节下面的“3.jQuery.get()和jQuery.post()”节中有解释这些值的含义。该选项没有默认值。当没有指定时,jQuery会检查响应中的Content-Type头来确定如何处理返回的数据。
contentType
指定请求的HTTP Content-Type头。默认是"application/x-www-form-urlencoded",这是HTML表单和绝大部分服务器脚本使用的正常值。如果将type选项设置为"POST",想发送纯文本或XML文档作为请求体时,需要设置该选项。
timeout
超时时间,单位是毫秒。如果设置了该选项,当请求没有在指定超时时间内完成时,请求会取消同时触发error回调,回调中的状态码参数为"timeout"。默认超时时间是0,表示除非请求完成,否则永远不会取消。
cache
对于GET请求,如果该选项设置为false,jQuery会添加一个“_=”参数到URL中,或者替换已经存在的同名参数。该参数的值是当前时间(毫秒格式)。这可以禁用基于浏览器的缓存,因为每次请求的URL都不一样。
ifModified
当该选项设置为true时,jQuery会为请求的每一个URL记录Last-Modified和If-None-Match响应头的值,并会在接下来的请求中为相同的URL设置这些头部信息。这可以使得,如果上次请求后URL的内容没有改变,则服务器会发送回HTTP 304"Not Modified"响应。默认情况下,该选项未设置,jQuery不会设置或记录这些头部信息。
jQuery将HTTP 304响应解释成"notmodified"状态码。"notmodified"状态不会被当成错误,传入success回调中的状态码是"notmodified",而不是通常的"success"状态码。因此,如果设置了ifModified选项,就必须在回调中检查该状态码——如果状态码是"notmodified",则第一个参数(响应数据)会是undefined。注意jQuery 1.4及其之前的版本,HTTP 304会被当成一个错误,"notmodified"状态码会被传入error回调中,而不是suceess回调中。请参考19.6.1节中的信息,以获取jQuery Ajax状态码的更多信息。
global
该选项指定jQuery是否应该触发上面描述的Ajax请求过程中的事件。默认值是true;设置该选项为false会禁用Ajax相关的所有事件。(参考19.6.4节获取事件的细节。)该选项的命名有些令人迷惑:取名为"global"是因为jQuery通常会全局地触发这些事件,而不是在具体某个对象上。
2.回调
下面的选项指定在Ajax请求的不同阶段调用的函数。success选项已经很熟悉了:这是传入给jQuery.getJSON()等方法的回调函数。注意jQuery也会将Ajax请求过程的消息当做事件发送(除非设置了global选项为false)。
context
该选项指定回调函数在调用时的上下文对象——就是this。该选项没有默认值,如果不设置,this会指向选项对象。设置context选项也会影响Ajax事件触发的方式(参考19.6.4节)。如果设置该选项,值应该为Window、Document或触发事件所在的Element。
beforeSend
该选项指定Ajax请求发送到服务器之前激活的回调函数。第一个参数是XMLHttpRequest对象,第二个参数是该请求的选项对象。beforeSend回调使得程序有机会在XMLHttpRequest对象上设置自定义HTTP头部。如果该回调函数返回false,Ajax请求会取消。注意跨域的"script"和"jsonp"请求没有使用XMLHttpRequest对象,因此不会触发beforeSend回调。
success
该选项指定Ajax请求成功完成时调用的回调函数。第一个参数是服务器发送的数据,第二个参数是jQuery状态码;第三个参数是用来发送该请求的XMLHttpRequest对象。如19.6.2节下面的“3.jQuery.get()和jQuery.post()”节所描述,第一个参数的类型取决于dataType选项或服务器响应的Content-Type头信息。如果类型是"xml",则第一个参数是Document对象。如果类型是"json"或"jsonp",第一个参数是服务器返回的JSON格式响应的解析结果。如果类型是"script",则响应内容是所加载脚本的文本内容(该脚本已经执行了,因此,在这种情况下通常可以忽略响应内容)。对于其他类型,响应内容直接就是请求资源的文本内容。
第二个参数的状态码通常是字符串"success",但是如果设置了ifModified选项,该参数就可能是"notmodified"。在这种情况下,服务器不发送响应并且不定义第1个参数。"script"和"jsonp"类型的垮域请求通过<script>元素而不是XMLHttpRequest执行,因此对于那些请求,不会定义第三个参数。
error
该选项指定Ajax请求不成功时调用的回调函数。该回调的第一个参数是该请求的XMLHttpRequest对象(如果用到的话)。第二个参数是jQuery的状态码。对于HTTP错误,该状态码可能是"error",对于超时,则是"timeout","parsererror"则表示解析服务器响应时出了问题。例如,如果XML文档或JSON对象不符合格式,则状态码为"parsererror"。在这种情况下,error回调的第三个参数是抛出的Error对象。注意dataType为"script"的请求在返回无效JavaScript代码时不会触发错误。脚本中的任何错误都会直接忽略,调用的回调则是success而不是error。
complete
该选项指定Ajax请求完成时激活的回调函数。每一个Ajax请求或者成功时调用success回调,或者失败时调用error回调。在调用success或error后,jQuery会调用complete回调。传给complete回调的第一个参数是XMLHttpRequest对象,第二个参数则是状态码。
3.不常用的选项和钩子
下述Ajax选项不经常使用。某些特定选项通常不可能设置,另一些选项则提供了自定义钩子,使得可以修改jQuery Ajax请求的默认处理方式。
async
脚本化的HTTP请求本身就是异步的。然而,XMLHttpRequest对象提供了一个选项,可用来阻塞当前进程,直到接收到响应。如果想开启这一阻塞行为,可以设置该选项为false。设置该选项不会更改jQuery.ajax()的返回值:如果有使用XMLHttpRequest对象的话,该函数会始终返回该对象。对于同步请求,可以自己从XMLHttpRequest对象中提取服务器的响应和HTTP状态码,如果想要获取jQuery解析的响应和状态码,可以指定一个complete回调(就和给异步请求指定的一样)。
dataFilter
该选项指定一个函数,用来过滤或预处理服务器返回的数据。第一个参数是从服务器返回的原始数据(字符串或XML请求返回的Document对象),第二个参数是dataType选项的值。如果指定该函数,则它必须返回一个值,该值会用来替换掉服务器的响应。注意dataFilter()函数会在JSON解析和脚本执行前执行。同时注意对于跨域的"script"和"jsonp"请求不会调用dataFilter()。
jsonp
当设置dataType选项为"jsonp"时,url或data选项通常会包含一个类似"jsonp=?"的参数。如果jQuery在URL或data选项中没有找到类似参数时,会使用该选项指定的名字插入一个。该选项的默认值是"callback"。在使用JSONP时,如果服务器需要一个不同的参数名,而URL或data选项中又没有指定时,需要设置该选项。请查看18.2节获取JSONP的更多细节。
jsonpCallback
对于dataType为"jsonp"的请求(或URL中带有类似"jsonp=?"这种JSONP参数的"json"请求),jQuery必须将URL中的“?”替换成包装函数名,服务器会将数据传递给该包装函数。通常,jQuery会根据当前时间来生成一个唯一的函数名。如果想用自己的函数来替代jQuery生成的,则可以设置该选项。但是,一旦这样做了,会阻止jQuery在触发正常事件时调用success或complete回调。
processData
当设置data选项为对象(或将对象作为第二个参数传递给jQuery.get()和相关方法)时,jQuery通常会将该对象转换成字符串,该字符串遵守标准的HTML"application/x-www-form-urlencoded"格式(参考19.6.2节下面的"2.jQuery.getJSON()"节)。如果想省略掉该步骤(比如想将Document对象作为POST请求体发送),请设置该选项为false。
scriptCharset
对于跨域的"script"和"jsonp"请求,会使用<script>元素,该选项用来指定<script>元素的charset属性值。该选项对正常的基于XMLHttpRequest的请求不会有任何作用。
tranditional
jQuery 1.4改变了数据对象序列化为"application/x-www-form-urlencoded"字符串的方式(细节请参考19.6.2节下面的"2.jQuery.getJSON()"节)。设置该选项为true,可以让jQuery回复到原来的方式。
username,password
如果请求需要密码验证,请使用这两个选项来指定用户名和密码。
xhr
该选项指定一个工厂函数,用来获取XMLHttpRequest对象。该工厂函数在调用时不带参数,而且必须返回一个实现了XMLHttpRequest API的对象。这个非常底层的钩子可以创建自己对XMLHttpRequest的包装,可以给方法添加特性或测量。
19.6.4 Ajax事件
19.6.3 节下面的“2.回调”节描述了jQuery.ajax()拥有4个回调选项:beforeSend、success、error和complete。除了分别激活这些指定的回调函数,jQuery的Ajax函数还会在Ajax请求的每一个相同阶段触发自定义事件。下面的表格展示了这些回调函数和相应的事件:
可以使用bind()方法和上表第二列中的事件类型字符串来注册这些自定义Ajax事件,也可以使用第三列中的事件注册方法来注册。ajaxSuccess()和其他方法的使用方式就与click()、mouseover()以及19.4.1节中的其他简单事件注册方法一样。
由于Ajax事件是自定义事件,是由jQuery而不是浏览器产生的,因此传递给事件处理程序的Event对象不是很有用。其实,ajaxSend、ajaxSuccess、ajaxError和ajaxComplete事件在触发时都带有其他参数。这些事件的处理程序激活时在event参数后都带有两个额外的参数。第一个额外参数是XMLHttpRequest对象,第二个额外参数是选项对象。例如,这意味着ajaxSend事件的处理程序可以向XMLHttpRequest对象添加自定义头,就和beforeSend回调可以做的一样。除了上面描述的两个额外参数,触发ajaxError事件时还会带有第三个额外参数。如果有的话,事件处理程序的第三个额外参数是Error对象,是在发生错误时抛出的。令人奇怪的是,这些Ajax事件并没有传入jQuery的状态码。例如,如果ajaxSuccess事件的一个处理程序需要区分"success"和"notmodified",则它需要从XMLHttpRequest对象中检查原始HTTP状态码。
上面表格中列举的最后两个事件与其他事件不同,最明显的是它们没有相应的回调函数,同时它们在触发时不带额外参数。ajaxStart和ajaxStop是一对表示与Ajax相关的网络活动开始和停止的事件。当jQuery没在执行任何Ajax请求时,如果开始一个新请求,它就会触发ajaxStart事件。如果在第一个请求还没完成时,其他请求就开始了,这些新请求不会触发新的ajaxStart事件。当最后一个挂起的Ajax请求完成并且jQuery不再执行任何网络活动时,会触发ajaxStop事件。这一对事件能很方便地用来显示和隐藏某些“加载中…”动画或网络活动的图标。例如:
$("#loading_animation").bind({
ajaxStart:function(){$(this).show();},
ajaxStop:function(){$(this).hide();}
});
这些ajaxStart和ajaxStop的事件处理程序可以绑定到任意文档元素上:jQuery是全局地触发它们(参考19.4.6节)而不是在某个特定元素上触发。其他4个Ajax事件,ajaxSend、ajaxSuccess、ajaxError和ajaxComplete,通常也是全局触发的,因此处理程序也可以绑定到任何元素上。然而,如果在调用jQuery.ajax()时设置了context选项,则这4个事件不会全局地触发,而会在context元素上触发。
最后,记住可以通过设置global选项为false来阻止jQuery触发任何Ajax相关的事件。尽管这个选项名很让人迷惑,但是设置global为false可以让jQuery不再在context对象上触发事件以及不再全局地触发事件。
19.7 工具函数
jQuery类库定义了不少工具函数(还有两个属性),在编写程序时挺有用。在下面的列表中你会发现,部分函数在ECMAScript(ES5)中已经有了等价形式。jQuery的函数比ES5早,并且可以工作在所有浏览器中。按照字母排序,将这些工具函数列举如下:
jQuery.browser
browser属性不是一个函数而是一个对象,可用于客户端嗅探(参见13.4.5节)。如果浏览器是IE,该对象会拥有一个msie属性,值为true。如果浏览器是Firefox或与其相关,会有一个值为true的mozilla属性。同样,在Safari和Chrome中,webkit属性为true;在Opera中,opera属性为true。除了与浏览器相关的属性,还有一个version属性,包含浏览器的版本号。尽量不要使用客户端嗅探,但是可以通过以下代码使用该属性来解决浏览器相关的bug:
if($.browser.mozilla&&parseInt($.browser.version)<4){//在此解决一个假设的Firefox bug…
}
jQuery.contains()
该函数接受两个文档元素作为参数。如果第一个元素包含第二个元素,则返回true;否则返回false。
jQuery.each()
和each()方法不同,each()方法只能遍历jQuery对象,而jQuery.each()工具函数可以遍历数组元素或对象属性。第一个参数是要遍历的数组或对象;第二个参数是要在每个数组元素或对象属性上调用的函数。该函数在调用时会带有两个参数:数组元素的序号或对象的属性名,以及数组元素的值或对象的属性值。函数中的this值和第二个参数是一样的。如果该函数返回false,jQuery.each()会停止当前遍历并立刻返回。jQuery.each()总是返回第一个参数的值。
jQuery.each()会使用普通的for/in循环来遍历对象属性,所以会遍历所有可枚举的属性,包括继承的属性。jQuery.each()在遍历数组元素时,会以序号从小到大来遍历,不会跳过稀疏数组中的undefined属性。
jQuery.extend()
该函数接受对象作为参数。它会将第二个及其以后参数对象的属性复制到第一个参数对象中,如果同名的属性在第一个参数对象中已经存在,则会覆盖它。该函数会忽略任何值为undefined或null的属性。如果仅传入了一个对象,该对象的属性会被复制到jQuery对象自身中。该对象的返回值是属性被复制到的对象。如果一个参数的值为true,会执行深拷贝:第三个(及其以后)对象的属性会被复制到第二个对象上。
该函数用来复制对象以及合并带有几组默认值的选项对象时非常有用:
var clone=jQuery.extend({},original);
var options=jQuery.extend({},default_optinos,user_options);
jQuery.globalEval()
该函数会在全局上下文中执行JavaScript代码字符串,就像它是<script>元素的内容一样。(实际上,jQuery实现该函数时,就是通过创建一个<script>元素并临时把它插入文档中来实现的。)[7]
jQuery.grep()
该函数和ES5中Array对象的filter()方法类似。它接受数组作为第一个参数,以及一个判断函数作为第二个参数,该判断函数会在数组的每一个元素上调用,调用时会传入元素值和元素序号作为参数。jQuery.grep()返回一个新数组,新数组由调用判断函数时返回true(或其他真值)的元素组成。如果给jQuery.grep()传入true作为第三个参数,则它会反转判断函数,返回的数组将会由判断函数调用时为false或其他假值的元素组成。
jQuery.inArray()
该函数和ES5中Array对象的indexOf()方法类似。它的第一个参数可以是任意值,第二个参数则是数组(或类数组对象),返回值是第一个参数值在数组中第一次出现的序号,如果该参数值不存在的话,则返回-1。
jQuery.isArray()
当参数是原生Array对象时,返回true。
jQuery.isEmptyObject()
当参数对象没有可枚举的属性时,返回true。
jQuery.isFunction()
当参数是原生Function对象时,返回true。注意,在IE8及以前版本中,window.alert()和window.attachEvent()等浏览器方法返回false。
jQuery.isPlainObject()
如果参数是“纯”对象,而不是某些特定类型或类的对象的实例时,返回true。
jQuery.makeArray()
如果参数是类数组对象,该函数会将对象的属性复制到一个新的(真)数组中,并返回该数组。如果参数不是类数组对象,该函数会仅返回一个新数组,该数组只包含传入的参数一个元素。
jQuery.map()
该函数和ES5中Array对象的map()方法类似。它接受数组或类数组对象作为第一个参数;第二个参数则为映射函数。每一个数组元素与其序号都会传入这映射函数中,返回值就是由映射函数返回的值组成的新数组。jQuery.map()与ES5 map()方法存在两点不同。首先,如果映射函数返回的是null,该值不会被包含在返回的数组中。其次,如果映射函数返回的是数组,该数组的元素会被添加到结果数组中,而不是数组本身中。
jQuery.merge()
该函数接受两个数组或类数组对象。它会将第二个参数的元素添加到第一个上面,并返回第一个参数。第一个数组会修改,第二个不会。可以使用该函数来浅拷贝类数组对象:
var clone=jQuery.merge([],original);
jQuery.parseJSON()
该函数会解析JSON格式的字符串,返回解析结果。当传入的格式有误时,它会抛出异常。在定义它的浏览器中jQuery使用标准的JSON.parse()函数。注意jQuery只定义JSON解析函数,而没有定义JSON序列化函数。
jQuery.proxy()
该函数和ES5中Function对象的bind()方法(参见8.7.4节)类似。它接受函数作为第一个参数,对象作为第二个参数,并返回一个新函数,该函数会作为第二个参数对象的方法调用。它没有像bind()方法那样实现参数的部分应用。
jQuery.proxy()在调用时还可以传入对象作为第一个参数,传入属性名作为第二个参数。该名称代表的属性值应该是一个函数。通过这种方式调用,函数jQuery.proxy(o,n)的返回值与jQuery.proxy(o[n],o)一样。
jQuery.proxy()的目的是用来与jQuery的事件处理程序绑定机制一起使用。如果绑定了一个代理函数,可以使用原始函数来解除绑定它。
jQuery.support
这个属性类似jQuery.browser,它用来做可移植的特性探测(参见13.4.3节),而不是脆弱的浏览器探测。jQuery.support的值是一个对象,该对象的属性都是布尔值,用来指明浏览器特性的存在情况。jQuery.support的绝大部分属性都是jQuery内部使用的底层特性。这可能会引起插件开发者的兴趣,但对应用开发者来说大部分都用途不大。一个例外是jQuery.support.boxModel:当浏览器使用CSS标准的"context-box"模型时,该属性为true,而在IE6和IE7的怪异模式下时为false(参考16.2.3节下面的“边框盒模型和box-sizing属性”节)。
jQuery.trim()
该函数和ES5中给字符串添加的trim()方法类似。它接受字符串作为唯一参数,返回的字符串开头和结尾处的空白字符都已移除。
19.8 jQuery选择器和选取方法
在本章中,我们已经使用了带有简单CSS选择器的jQuery选取函数:$()。现在是时候深入了解jQuery选择器语法,以及一些提取和扩充选中元素集的方法了。
19.8.1 jQuery选择器
在CSS3选择器标准草案定义的选择器语法中,jQuery支持相当完整的一套子集,同时还添加了一些非标准但很有用的伪类。15.2.5节描述过基本的CSS选择器。在此我们会重复一下,并增加对更多高级选择器的阐释。注意:本节讲述的是jQuery选择器。其中有不少选择器(但不是全部)可以在CSS样式表中使用。
选择器语法有三层结构。你肯定已经见过选择器中最简单的形式。"#test"选取id属性为"test"的元素。"blockquote"选取文档中的所有<blockquote>元素,而"div.note"则选取所有class属性为"note"的<div>元素。简单选择器可以组合成“组合选择器”,比如"div.note>p"和"blockquote i",只要用组合字符做分隔符就行。简单选择器和组合选择器还可以分组成逗号分隔的列表。这种选择器组是传递给$()函数最常见的形式。在解释组合选择器和选择器组之前,我们必须先了解简单选择器的语法。
1.简单选择器
简单选择器的开头部分(显式或隐式地)是标签类型声明。例如,如果只对<p>元素感兴趣,简单选择器可以用"p"开头。如果选取的元素和标签名无关,则可以使用通配符“*”号来代替。如果选择器没有以标签名或通配符开头,则隐式含有一个通配符。
标签名或通配符指定了备选文档元素的一个初始集。在简单选择器中,标签类型声明之后的部分由零个或多个过滤器组成。过滤器从左到右应用,和书写顺序一致,其中每一个都会缩小选中元素集。表19-1列举了jQuery支持的过滤器。
注意:表19-1中列举的部分选择器在圆括号中接受参数。例如,下面这个选择器选取的元素在其父节点的子元素中排行第1或第2等,只要它们含有"JavaScript"单词,就不包含<a>元素。
p:nth-child(3n+1):text(JavaScript):not(:has(a))
通常来说,指定标签类型前缀,可以让过滤器的运行更高效。例如,不要简单使用":radio"来选取单选框按钮,使用"input:radio"会更好。ID过滤器是个例外,不添加标签前缀时它会更高效。例如,选择器"#address"通常比更明确的"form#address"更高效。
2.组合选择器
使用特殊操作符或“组合符”可以将简单选择器组合起来,表达文档树中元素之间的关系。表19-2列举了jQuery支持的组合选择器。这些组合选择器与CSS3支持的组合选择器是一样的。
下面是组合选择器的一些例子:
"blockquote i"//匹配<blockquote>里的<i>元素
"ol>li"//<li>元素是<ol>的直接子元素
"#output+*"//id="output"元素后面的兄弟元素
"div.note>h1+p"//紧跟<h1>的<p>元素,在<div class="note">里面
注意组合选择器并不限于组合两个选择器:组合三个甚至更多选择器也是允许的。组合选择器从左到右处理。
3.选择器组
传递给$()函数(或在样式表中使用)的选择器就是选择器组,这是一个逗号分隔的列表,由一个或多个简单选择器或组合选择器构成。选择器组匹配的元素只要匹配该选择器组中的任何一个选择器就行。对我们来说,一个简单选择器也可以认为是一个选择器组。下面是选择器组的一些例子:
"h1,h2,h3"//匹配<h1>、<h2>和<h3>元素
"#p1,#p2,#p3"//匹配id为p1、p2或p3的元素
"div.note,p.note"//匹配class="note"的<div>和<p>元素
"body>p,div.note>p"//<body>和<div class="note">的<p>子元素
注意:CSS和jQuery选择器语法允许在简单选择器的某些过滤器中使用圆括号,但并不允许使用圆括号来进行更常见的分组。例如,不能把选择器组或组合选择器放在圆括号中并且当成简单选择器:
(h1,h2,h3)+p//非法
h1+p,h2+p,h3+p//正确的写法
19.8.2 选取方法
除了$()函数支持的选择器语法,jQuery还定义了一些选取方法。本章中我们已看到过的大部分jQuery方法都是在选中元素上执行某种操作。选取方法不一样:它们会修改选中元素集,对其进行提取、扩充或仅作为新选取操作的起点。
本节描述这些选取方法。你会注意到这些选取方法中的多数提供的功能与选择器语法的功能是一样的。
提取选中元素最简单的方式是按位置提取。first()返回的jQuery对象仅包含选中元素中的第一个,last()返回的jQuery对象则只包含最后一个元素。更通用的是,eq()方法返回的jQuery对象只包含指定序号的单个选中元素。(在jQuery 1.4中,负序号也是允许的,会从选区的末尾开始计数。)注意这些方法返回的jQuery对象只含有一个元素。这与常见的数组序号是不一样的,数组序号返回的单一元素没有经过jQuery包装:
var paras=$("p");
paras.first()//仅选取第一个<p>元素
paras.last()//仅选取最后一个<p>
paras.eq(1)//选取第二个<p>
paras.eq(-2)//选取倒数第二个<p>
paras[1]//第二个<p>元素自身
通过位置提取选区更通用的方法是slice()。jQuery的slice()方法与Array.slice()方法类似:前者接受开始和结束序号(负序号会从结尾处计算),返回的jQuery对象包含从开始到结束序号(但不包含结束序号)处的元素集。如果省略结束序号,返回的对象会包含从开始序号起的所有元素:
$("p").slice(2,5)//选取第3个、第4个和第5个<p>元素
$("div").slice(-3)//选取最后3个<div>元素
filter()是通用的选区过滤方法,有3种调用方式:
·传递选择器字符串给filter(),它会返回一个jQuery对象,仅包含也匹配该选择器的选中元素。
·传递另一个jQuery对象给filter(),它会返回一个新的jQuery对象,该对象包含这两个jQuery对象的交集。也可以传递元素数组甚至单一文档元素给filter()。
·传递判断函数给filter(),会为每一个匹配元素调用该函数,filter()则返回一个jQuery对象,仅包含判断函数为true(或任意真值)的元素。在调用判断函数时,this值为当前元素,参数是元素序号。(参考19.7节中的jQuery.grep())
$("div").filter(".note")//与$("div.note")一样
$("div").filter($(".note"))//与$("div.note")一样
$("div").filter(function(idx){return idx%2==0})//与$("div:even")一样
not()方法与filter()一样,除了含义与filter()相反。如果传递选择器字符串给not(),它会返回一个新的jQuery对象,该对象只包含不匹配该选择器的元素。如果传递jQuery对象、元素数组或单一元素给not(),它会返回除了显式排除的元素之外的所有选中元素。如果传递判断函数给not(),该判断函数的调用就与在filter()中一样,只是返回的jQuery对象仅包含那些使得判断函数返回false或其他假值的元素:
$("div").not("#header,#footer");//除了两个特殊元素之外的所有<div>元素
在jQuery 1.4中,提取选区的另一种方式是has()方法。如果传入选择器,has()会返回一个新的jQuery对象,仅包含有子孙元素匹配该选择器的选中元素。如果传入文档元素给has(),它会将选中元素集调整为那些是指定元素祖先节点的选中元素:
$("p").has("a[href]")//包含链接的段落
add()方法会扩充选区,而不是对其进行过滤或提取。可以将传给$()函数的任何参数(除了函数)照样传给add()方法。add()方法会返回原来的选中元素,加上传给$()函数的那些参数所选中(或创建)的那些元素。add()会移除重复元素,并对该组合选区进行排序,以便里面的元素按照文档中的顺序排列:
//选取所有<div>和所有<p>元素的等价方式
$("div,p")//使用选择器组
$("div").add(p)//给add()传入选择器
$("div").add($("p"))//给add()传入jQuery对象
var paras=document.getElementsByTagName("p");//类数组对象
$("div").add(paras);//给add()传入元素数组
1.将选中元素集用做上下文
上面描述的filter()、add()、和not()方法会在各自的选中元素集上执行交集、并集和差集运算。jQuery还定义一些其他选取方法可将当前选中元素集作为上下文来使用。对选中的每一个元素,这些方法会使用该选中元素作为上下文或起始点来得到新的选中元素集,然后返回一个新的jQuery对象,包含所有新的选中元素的并集。与add()方法类似,会移除重复元素并进行排序,以便元素会按照在文档中出现的顺序排列好。
该类别选取方法中最通用的是find()。它会在每一个当前选中元素的子孙元素中寻找与指定选择器字符串匹配的元素,然后它返回一个新的jQuery对象来代表所匹配的子孙元素集。注意这些新选中的元素不会并入已存在的选中元素集中。同时注意find()和filter()不同,filter()不会选中新元素,只是简单地将当前选中的元素集进行缩减:
$("div").find("p")//在<div>中查找<p>元素,与$("div p")相同
该类别中的其他方法返回新的jQuery对象,代表当前选中元素集中每一个元素的子元素、兄弟元素或父元素。大部分都接受可选的选择器字符串作为参数。不传入选择器时,它们会返回所有子元素、兄弟元素或父元素。传入选择器时,它们会过滤元素集,仅返回匹配的。
children()方法返回每一个选中元素的直接子元素,可以用可选的选择器参数进行过滤:
//寻找id为"header"和"footer"元素的子节点元素中的所有<span>元素
//与$("#header>span,#footer>span")相同
$("#header,#footer").children("span")
contents()方法与children()方法类似,不同的是它会返回每一个元素的所有子节点,包括文本节点。如果选中元素集中有<iframe>元素,contents()还会返回该<iframe>内容的文档对象。注意contents()不接受可选的选择器字符串参数——因为它返回的文档节点不完全是元素,而选择器字符串仅用来描述元素节点。
next()和prev()方法返回每一个选中元素的下一个和上一个兄弟元素(如果有的话)。如果传入了选择器,会只选中匹配该选择器的兄弟元素:
$("h1").next("p")//与$("h1+p")相同
$("h1").prev()//<h1>元素前面的兄弟元素
nextAll()和prevAll()返回每一个选中元素前面或后面的所有兄弟元素(如果有的话)。siblings()方法则返回每一个选中元素的所有兄弟元素(选中元素本身不是自己的兄弟元素)。如果给这些方法传入选择器,则只会返回匹配的兄弟元素:
$("#footer").nextAll("p")//紧跟#footer元素的所有<p>兄弟元素
$("#footer").prevAll()//#footer元素前面的所有兄弟元素
从jQuery 1.4开始,nextUntil()和prevUntil()方法接受一个选择器参数,会选取选中元素后面或前面的所有兄弟元素,直到找到某个匹配该选择器的兄弟元素为止。如果省略该选择器,这两个方法的作用就和不带选择器的nextAll()和prevAll()一样。
parent()方法返回每一个选中元素的父节点:
$("li").parent()//列表元素的父节点,比如<ul>和<ol>元素
parents()方法返回每一个选中元素的祖先节点(向上直到<html>元素)。parent()和parents()都接受一个可选的选择器字符串参数:
$("a[href]").parents("p")//含有链接的<p>元素
parentsUntil()返回每一个选中元素的祖先元素,直到出现匹配指定选择器的第一个祖先元素。closest()方法必须传入一个选择器字符串,会返回每一个选中元素的祖先元素中匹配该选择器的最近一个祖先元素(如果有的话)。对该方法而言,元素被认为是自身的祖先元素。在jQuery 1.4中,还可以给closest()传入一个祖先元素作为第二个参数,用来阻止jQuery往上查找时超越该指定元素:
$("a[href]").closest("div")//包含链接的最里层<div>
$("a[href]").parentsUntil(":not(div)")//所有包裹<a>的<div>元素
2.恢复到之前的选中元素集
为了实现方法的链式调用,很多jQuery对象的方法最后都会返回调用对象。然而本节讲述的方法都返回新的jQuery对象。可以链式调用下去,但必须清晰地意识到,在链式调用的后面所操作的元素集,可能已经不是该链式调用开始时的元素集了。
实际情况还要复杂些。当这里所描述的选取方法在创建或返回一个新的jQuery对象时,它们会给该对象添加一个到它派生自的旧jQuery对象的内部引用。这会创建一个jQuery对象的链式表或栈。end()方法用来弹出栈,返回保存的jQuery对象。在链式调用中调用end()会将匹配元素集还原到之前的状态。考虑以下代码:
//寻找所有<div>元素,然后在其中寻找<p>元素
//高亮显示<p>元素,然后给<div>元素添加一个边框
//首先,不使用链式调用
var divs=$("div");
var paras=div.find("p");
paras.addClass("highlight");
divs.css("border","solid black 1px");//下面展现如何使用链式调用来实现
$("div").find("p").addClass("highlight").end().css("border","solid black 1px");//还可以将操作调换顺序来避免调用end()
$("div").css("border","solid block 1px").find("p").addClass("highlight");
如果想手动定义选中元素集,同时保持与end()方法的兼容,可以将新的元素集作为数组或类数组对象传递给pushStack()方法。指定的元素会成为新的选中元素,之前选中的元素集则会压入栈中,之后可以用end()方法还原它们:
var sel=$("div");//选取所有<div>元素
sel.pushStack(document.getElementsByTagName("p"));//修改为所有<p>元素
sel.end();//还原为<div>元素
既然我们已经讲解了end()方法及其使用的选区栈,就有最后一个方法需要讲解。andSelf()返回一个新的jQuery对象,包含当前的所有选中元素,加上之前的所有选中元素(会去除重复的)。andSelf()和add()方法一样,或许"addPrev"是一个更具描述性的名字。作为例子,考虑上面代码的下述变化:高亮显示<p>元素及其父节点中的<div>元素,然后给这些<div>元素添加边框:
$("div").find("p").andSelf().//寻找<div>中的<p>,合并起来
addClass("highlight").//都高亮
end().end().//弹出栈两次,返回$("div")
css("border","solid black 1px");//给divs添加边框
19.9 jQuery的插件扩展
jQuery的写法使得添加新功能很方便。添加新功能的模块称为插件(plug-in),可以在这里找到很多插件:http://plugins.jquery.com。jQuery插件是普通的JavaScript代码文件,在网页中使用时,只需要用<script>元素引入就好,就和引用任何其他JavaScript类库一样(注意,必须在jQuery之后引入插件)。
开发jQuery插件非常简单。关键点是要知道jQuery.fn是所有jQuery对象的原型对象。如果给该对象添加一个函数,该函数会成为一个jQuery方法。例子如下:
jQuery.fn.println=function(){//将所有参数合并成空格分隔的字符串
var msg=Array.prototype.join.call(arguments,"");//遍历jQuery对象中的每一个元素
this.each(function(){//将参数字符串作为纯文本添加到每一个元素后面,并添加一个<br/>
jQuery(this).append(document.createTextNode(msg).append("<br/>"));
});//返回这个未加修改的jQuery对象,以便链式调用
return this;
}
通过上面对jQuery.fn.println()函数的定义,我们可以在任何jQuery对象上类似如下调用println()方法了:
$("#debug").println("x=",x,";y=",y);
这是添加新方法到jQuery.fn中的常见开发方式。如果发现自己在使用each()方法“手动”遍历jQuery对象中的元素,并在元素上执行某些操作时,就可以问问自己,是否可以将代码重构一下,使得这些each()回调移动到一个扩展方法里[8]。在开发扩展功能时,如果遵守基本的模块化代码实践,以及遵守jQuery特定的一些传统约定,就可以将该扩展称为插件,并与他人分享。下面是一些值得留意的jQuery插件约定:
·不要依赖$标识符:包含的页面有可能调用了jQuery.noConflict()函数,$()可能不再等同于jQuery()函数。在上面这种简短的插件里,只要使用jQuery代替$就行。如果开发的扩展很长,则最好用一个匿名函数将扩展代码都包装起来,以避免创建全局变量。如果这样做,可以将jQuery作为参数传递给匿名函数,参数名采用$:
(function($){//带有参数名为$的匿名函数
//在此书写插件代码
}(jQuery));//使用jQuery对象作为参数调用该匿名函数
·如果插件代码不返回自己的值,请确保返回jQuery对象以便链式调用。通常这就是this对象,只要不加修改地返回即可。在上面的例子中,方法末尾是"return this;"代码行。遵循jQuery的另一个习俗,可以让上面的方法更简短些(可读性低一些):返回each()方法的结果。这样,println()方法会包含代码"return this.each(function(){…});"。
·如果扩展方式拥有两个以上参数或配置选项,请允许用户能使用对象的方式传递选项(就如我们在19.5.2节看到的animate()方法和在19.6.3节看到的jQuery.ajax()函数一样)。
·不要污染jQuery方法的命名空间。优雅的jQuery插件会用一套有用的API定义最少量的方法。通常,一个jQuery插件只会在jQuery.fn上定义一个方法。该方法会接受字符串作为第一个参数,然后将该字符串作为函数名解析,然后将剩余参数传给该解析函数。当可以将插件限定为一个方法时,该方法名应该与插件同名。如果需要定义多个方法,则使用插件名作为每一个方法名的前缀。
·如果插件需要绑定事件处理程序,请将所有这些处理程序放在事件命名空间中(参见19.4.4节)。使用插件名作为命名空间名。
·如果插件需要使用data()方法与元素关联数据,请将所有数据值放在单一对象中,然后用与插件名相同的键值将该对象作为单一值存储。
·用"jquery.plugin.js"这种文件命名方式保存插件代码到一个文件中(将"plugin"替换为插件名)。
插件可以给jQuery自身增加函数来添加新的工具函数。例如:
//该方法输出其参数(使用println()插件方法)
//到id为"debug"的元素上。如果不存在该元素,则创建一个并添加到文档中
jQuery.debug=function(){
var elt=jQuery("#debug");//查找#debug元素
if(elt.length==0){//如果它不存在则创建之
elt=jQuery("<div id='debug'><h1>Debugging Output</h1></div>");
jQuery(document.body).append(elt);
}
elt.println.apply(elt,arguments);//将参数输出到元素中
};
除了定义新方法,还可以扩展jQuery类库的其他部分。例如,在19.5节中,我们已经看到可以通过给jQuery.fx.speeds添加属性来扩充新的动画时长名(除了"fast"和"slow"),也可以通过给jQuery.easing添加属性来添加新的缓动函数。插件甚至可以扩展jQuery的CSS选择器引擎!可以通过给jQuery.expr[':']对象添加属性来添加新的伪类过滤器(比如:first和:input)。下面这个例子定义了一个新的:draggable过滤器,可用来仅返回拥有draggable=true属性的元素:
jQuery.expr[':'].draggable=function(e){return e.draggable===true;};
使用上面定义的这个选择器,可以用$("img:draggable")来选取可拖曳的图片,而不用使用冗长的$("img[draggable=true]")。
从上面的代码中可以看到,自定义选择器函数的第一个参数是候选的DOM元素。如果该元素匹配选择器,则返回true;否则返回false。许多自定义选择器只需要这一个元素参数,但实际上在调用它们时传入了4个参数。第二个参数是整数序号,表示当前元素在候选元素数组中的位置。候选元素数组作为第4个参数传入,选择器不应该修改它。第三个参数很有趣的:这是调用RegExp.exec()方法后返回的数组。如果有的话,该数组的第4个元素(序号是3)是伪类过滤器后面的圆括号中的值。圆括号和里面的任何引号都去除了,只留下参数字符串。下面是一个例子,用来说明如何实现一个:data(x)伪类,该伪类只在元素拥有data-x属性时返回true(参考15.4.3节):
jQuery.expr[':'].data=function(element,index,match,array){//注意:IE7及其以下版本不支持hasAttribute()
return element.hasAttribute("data-"+match[3]);
};
19.10 jQuery UI类库
jQuery限定自己只提供核心DOM、CSS、事件处理以及Ajax功能。这提供了一个很棒的基础,可用来构建高层面的抽象,比如用户界面组件,jQuery UI类库就是这么做的。对jQuery UI做全面讲述不属于本书的范畴,在此我们只会简单概要地介绍它。该类库及其文档可以在这里找到:http://jqueryui.com。
类如其名,jQuery UI定义了一些用户界面组件:输入区域的自动完成、输入日期的日期选取器、用来组织信息的手风琴和标签页、可视化展现数字的滑块和进度条,以及用来和用户紧急通信的模态对话框。除了这些组件,jQuery UI还实现了更一般化的“交互”,可以使得任何文档元素轻松就实现可拖曳、可放置、可改变大小、可选取或可排序。最后,jQuery UI还给jQuery自身的效果方法提供了一些新的视觉效果方法(还使得可以变化颜色),同时定义很多新的缓动函数。
可以把jQuery UI想象成一组相关的jQuery插件,只是最后打包成一个JavaScript文件。要使用它很简单,在网页中,将jQuery UI脚本放在jQuery代码后面引入进来就行。下载页面http://jqueryui.com允许选取计划使用的组件,然后会构建一个自定义的下载包,与整个jQuery UI类库相比,这可以减少页面的加载时间。
jQuery UI是完全皮肤化的,它的皮肤直接采用CSS文件的形式。因此除了要加载jQuery UI的JavaScript代码到网页中,还需要引入选中皮肤的CSS文件。jQuery UI站点标注了一些预先打包好的皮肤包,还有一个“皮肤工作坊”页面,可以让你自定义和下载自己的皮肤。
jQuery UI组件和交互功能采用jQuery插件的方法构建,每一个都定义一个jQuery方法。通常,在已存在的文档元素中调用该方法时,会将该元素转化为组件。例如,要改变输入文本,以便在单击或聚焦文本输入框时它要弹出一个日期选取组件,直接用下面的代码调用datapicker()就行:
//将class="date"的<input>元素转化成日期选取组件
$("input.date").datepicker();
要想灵活自如地使用jQuery UI组件,需要熟悉三件东西:它的配置选项、它的方法以及它的事件。所有jQuery UI组件都是可配置的,有一些组件有很多配置选项。可以通过给组件方法传递选项对象(和在动画操作里,传递选项对象给animate()类似)来自定义组件的行为和外观。
jQuery UI组件通常会定义至少有几个“方法”来与组件交互。但是,为了避免jQuery方法的迅速增多,jQuery UI组件不会将它们的“方法”定义成真正的方法。每个组件只会有一个方法(与上例中的datapicker()方法一样)。当需要调用组件的一个“方法”时,需要给该组件定义的真正方法传递预期“方法”的名称。例如,想要禁用日期选取组件,不能调用disableDatapicker()方法,而需要调用datepicker("disable")。
jQuery UI组件通常会定义自定义事件,响应用户交互时触发它们。可以使用常用的bind()方法来给这些自定义事件绑定事件处理程序,通常还可以将事件处理程序函数作为选项对象的属性,该选项对象会传递给组件方法。事件处理程序的第一个参数依旧是Event对象。某些组件还会传递一个"UI"对象作为事件处理程序的第二个参数。该对象通常提供了当前组件的状态信息。
注意,jQuery UI文档中有时描述的“事件”并不是真正的自定义事件,可能描述为回调函数会更好,这些回调函数是通过配置选项对象设置的。例如,日期选取组件支持不少回调函数,可以在不同的时间点调用它。但是,这些函数中没有一个拥有标准的事件处理程序签名,不能使用bind()来为这些“事件”注册处理程序。正确的做法是,在初始调用datapicker()方法,给组件传递配置选项时,就指定合适的回调函数。
[1]本书不涵盖Prototype、YUI、dojo等其他通用类库。搜索"JavaScript libraries"可以找到更多类库。
[2]如果你在自己的代码中有使用$作为变量,或者引入了Prototype等使用$作为全局变量的类库,这时,为了避免冲突,可以调用jQuery.noConflict()来释放$变量,让其指向原始值。
[3]在撰写本章时,jQuery的最新版本是1.4.2。在本书准备出版时,jQuery刚发布1.5。1.5版本最大的变化是Ajax工具函数,这将在19.6节提及。在本书翻译成中文时,jQuery已发布1.6.2版本,不过万物归宗,学会1.4.2版本如何使用,跟进jQuery的最新版本,都是很简单的事情了。
[4]textColor不对,应该是color。
[5]jQuery使用术语"bind"来表示事件处理程序的注册。ECMA Script 5(以及不少JavaScript框架)给函数定义了bind()方法(参见8.7.4节),使用"bind"术语来表示对象与函数之间的关联,这些函数会在这些对象上调用。Function.bind()方法的jQuery版本是一个名为jQuery.proxy()的工具函数,在19.7节中会讲解它。
[6]jQuery的实现方式不支持非数值动画,但有其他实现方案,比如传入自定义的CSS属性变化函数。
[7]在最新版本的jQuery里,已优化了实现,采用eval和execScript来执行。
[8]jQuery插件的这种扩展方式是全局性的,带来方便的同时,也污染了jQuery对象,容易造成潜在的冲突。译者并不推荐“随时想着”使用这种扩展方式。