附录A 关于 $(document).ready()函数

A.1 $(document).ready()函数介绍

学习 jQuery的第1个步骤就应该学习$(document).ready()函数。例如要在页面上运行一个JavaScript 函数,那么就应该将它写在$(document).ready()函数内。在该函数内的所有代码都将在DOM加载完毕后,页面全部内容(包括图片等)完全加载完毕前被执行。

jQuery代码如下:

[插图]

$(document).ready()函数相比其他获得JavaScript事件并执行相应事件的函数有很多优势。

该函数不需要在HTML代码中进行任何“行为”标记,就可以分离JavaScript代码到每一个独立的文件中。因此很容易对与内容无关的代码进行维护。当光标在一个链接上悬停时,在浏览器的状态栏中会出现“javascript:void()”的信息,该信息就是由于在一个标签中直接写入一个事件而产生的。

在一些传统的页面中,可能会在标签里有onload属性,该属性限定只能执行一个函数,并且同时也将“行为”标记引入到了页面内容中。Jeremy Keith所著的《DOM Scripting》展示了如何创建一个addLoadEvent函数以便将JavaScript分离成单独的文件,并允许加载多个函数。但是它需要一定数量且复杂的代码去实现这个功能。同时,用它加载的函数,也是必须要等页面所有内容被加载完毕后才执行。由于以上种种原因本书选择了更为先进的$(document).ready()函数。

$(document).ready()函数括号中的所有代码都会提前(只要DOM在浏览器中被注册完毕)被执行,而不是在页面所有内容(例如图片等占用带宽的内容)加载完毕之后才执行事件。它允许用户在第一眼看到元素的时候,就能立即看到元素产生的一些隐藏效果、显示效果和其他效果。

A.2 多个$(document).ready()函数

本节介绍$(document).ready()函数的另一个功能,即它可以被多次使用。如果不太在意代码的大小,那么可以将$(document).ready()函数放到所有的JavaScript文件中。无论函数是在一个文件中还是在多个文件中,它都可以将这些函数归组。

例如在项目中,每个页面中都引用了一个公共.js文件,并且每一个.js文件仅仅在首页中被引用,而且它们都需要调用$(document).ready()函数。那么可以在首页的<head>标签中,同时加载3个JavaScript文件,jQuery代码如下:

[插图]

然后可以在每一个独立的.js文件中反复调用该函数,jQuery代码如下:

[插图]

最后需要注意的是,可以使用$(function(){…})来代替$(document).ready()函数,作为它的缩写形式,缩写方式如下:

[插图]

附录B Firebug

B.1 概述

Firebug是一个用于网站前端开发的工具,它是Firefox浏览器的一个扩展插件。它可以调试JavaScript、查看DOM、分析CSS、监控网络流量以及进行Ajax交互等。它提供了几乎前端开发需要的全部功能。官方网站是 www.getfirebug.com/

1.Firebug特色

  • 查看和编辑HTML。
  • 动态修改CSS样式。
  • 可视化的CSS距离调整。
  • 监控网络行为。
  • 分析与调试JavaScript。
  • 快速发现错误。
  • 查看DOM。
  • 即时执行JavaScript代码。
  • 记录JavaScript日志。

2.如何获取Firebug

因为它是 Firefox 浏览器的一个扩展插件,所以首先需要下载 Firefox 浏览器,读者可以访问www.mozilla.com下载并安装Firefox浏览器。本书以Firefox 11.0为例。在安装完Firefox浏览器之后,用它访问https://addons.mozilla.org/zh-CN/firefox/addon/1843,进入图B-1所示的页面。点击“添加到Firefox”按钮,会弹出图B-2所示的对话框,然后单击“立即安装”按钮,最后重新启动Firefox浏览器即可完成安装。

[插图]

图B-1 Firebug下载

[插图]

图B-2 Firebug安装

B.2 主面板简介

安装完成后,在 Firefox 浏览器的地址后方就会有一个小虫子的图标[插图]。单击该图标后即可展开Firebug的控制台,也可以通过快捷键<F12>来打开控制台。如果有多个显示器或者屏幕比较大,需要Firebug能够独立打开一个窗口而不占用Firefox页面底部的空间,可以用通过快捷键Ctrl+F12来呼出独立的Firebug窗口。如图B-3所示。

[插图]

图B-3 Firebug面板

从图B-3中可以看出,Firebug总共包含6个面板,分别是控制台、HTML、CSS、脚本、DOM和网络。

  • 控制台面板

用于记录日志、概览、错误提示和执行命令行,同时也用于Ajax的调试。

  • HTML面板

用于查看HTML元素,可以实时地编辑HTML和改变CSS样式。它包含3个子面板,分别是样式、布局和DOM面板。

  • CSS面板

用于查看所有页面上的CSS文件,可以动态地修改CSS样式。由于HTML面板中已经包含了一个CSS面板,因此该面板将很少用到。

  • 脚本面板

用于显示JavaScript文件及其所在的页面。也可以用来显示JavaScript的Debug调试,包含3个子面板,分别是监控、堆栈和断点。

  • DOM面板

用于显示页面上的所有对象。

  • 网络面板

用于监视网络活动。可以帮助查看一个页面的载入情况,包括文件下载占用的时间和文件下载出错等信息,也可以用于监视Ajax行为。

B.2.1 控制台面板

1.控制台面板概览

此面板可以用于记录日志,也可以用于输入脚本的命令行。

2.记录日志

Firebug提供如下几个常用的记录日志的函数。

console.log:简单的记录日志。
console.debug:记录调试信息,并且附上行号的超链接。
console.error:在消息前显示错误图标,并且附上行号的超链接。
console.info:在消息前显示信息图标,并且附上行号的超链接。
console.warn:在消息前显示警告图标,并且附上行号的超链接。

在空白的html页面中,向<body>标签里加入<script>标签,代码如下:

[插图]

执行代码后可以在Firebug中看到图B-4所示的效果。

[插图]

图B-4 执行后的效果

以前习惯了使用alert来调试程序,然而在Firebug下可以使用console。

3.格式化字符串输出和多变量输出

这个功能类似于C语言中的语法,可以在console记录日志的方法里使用。

  • %s: 字符串。
  • %d,%i: 数字。
  • %f: 浮点数。
  • %o: 链接对象。

同时,这几个函数支持多个变量。代码如下:

[插图]

运行代码后,效果如图B-5所示。

[插图]

图B-5 console.log运行效果

Firebug控制台还提供了其他功能,例如检测函数执行时间、消息分组、测试驱动、跟踪、计数以及查看JavaScript概况等。更多资料可以访问网址:http://getfirebug.com/logging

4.面板内的子菜单

在图B-5中,在控制台面板内有一排子菜单,分别是清除、保持、概况、所有等。

“清除”用于清除控制台中的内容。“保持”则是把控制台中的内容保存,即使刷新了还在。“所有”则是显示全部的信息。后面的“错误”,“警告”,“消息”,“调试信息”菜单则是对所有进行了一个分类。

“概况”菜单用于查看函数的性能。通过一个例子来演示这个功能,HTML代码如下:

[插图]

打开页面后,先启用Firebug控制台面板,然后单击“概况”菜单,如图B-6所示。

[插图]

图B-6 单击“概况”菜单后

从上图中可以看到,Firebug中出现一行字,即“概况收集中。再次单击“概况”查看结果。”接着,单击“执行循环1”按钮1次,“执行循环2”按钮2次,“执行循环3”按钮3次,并再次单击“概况”菜单,即可看到图B-7所示的效果。

[插图]

图B-7 再次单击“概况”菜单后

可以看到Firebug显示出了非常详细的报告。包括每个函数的函数名、调用次数,占用时间的百分比、占用时间、时间、平均时间、最小时间、最大时间以及所在文件的行数等信息。

5.Ajax调试

控制台面板也可用于Ajax的调试,在一定程度上可以取代网络面板。这一段涉及Ajax交互,故引入了jQuery库。同时由于本地环境下Ajax创建的XMLHttpRequest对象无法跨域访问到网络的资源,因此需要搭建服务器环境。代码如下:

[插图]

直接单击“进行Ajax传递”按钮,即可在Firebug的控制台中看到本次Ajax的HTTP请求头信息和服务器响应的头信息,如图B-8所示。它会显示出本次使用Ajax的GET方法、地址、耗时以及调用Ajax请求的代码行数。最重要的是有4个标签页,即参数、头信息、响应、HTML。第1个标签用于查看传递给服务器的参数;第2个标签用于查看响应头信息和请求头信息;第3个标签用于查看服务器返回的内容。第4个标签则是查看服务器返回的HTML 结构。进行 Ajax交互编程时,以上功能是非常有用的。读者可以尝试改变文本框中传递的参数,再次单击“进行Ajax传递”按钮,观察Firebug中的变化。

[插图]

图B-8 Ajax请求信息

如果看不到任何信息出现,可能是将此功能关闭了,可以单击“控制台”旁边的下拉箭头,将“显示XMLHttpRequests”前面的勾勾选上,如图B-9所示。

[插图]

图B-9 显示XMLHttpRequests

B.2.2 HTML面板

1.查看和修改HTML代码

HTML面板的强大之处就是能查看和修改HTML代码,而且这些代码都是经过格式化的。编写如下的代码来讲解该面板。

[插图]

在Firebug中切换到HTML面板,可以看到图B-10所示的页面。

[插图]

图B-10 初始化效果

在HTML控制台的左侧可以看到整个页面当前的文档结构,可以通过单击“+”来展开。当单击相应的元素时,右侧面板中就会显示出当前元素的样式、布局以及DOM信息。而当光标移动到HTML 树中相应元素上时,上面页面中相应的元素将会被高亮显示。例如将光标移动到<form>标签上时,显示效果如图B-11所示。

[插图]

图B-11 将光标移动到form元素上后

在页面中蓝色部分表示元素本身,紫色表示 padding部分,而黄色表示 margin 部分。同时可以实时地添加、修改和删除HTML节点以及属性,如图B-12所示。另外,单击script节点还可以直接查看脚本,此处的脚本无论是内嵌在 HTML 中还是外部导入的,都可以查看到。同样这也适用于<style>标签内嵌或者导入的CSS样式和动态创建的HTML代码。

[插图]

图B-12 实时编辑HTML元素

2.查看(Inspect)

利用查看(Inspect)功能,可以快速地寻找到某个元素的HTML结构,如图B-13所示。

[插图]

图B-13 查看(Inspect)功能

例如当单击“Inspect”按钮后,用鼠标在网页中选中一个元素时,元素会被一个蓝色的框框住,同时下面的HTML面板中相应的HTML树也会展开并且高亮显示。再次单击后即可退出该模式,并且底部的HTML 树也保持在这个状态。通过这个功能,可以快速寻找页面内的元素,调试和查找相应代码非常方便。刷新网页后,页面显示的仍然是用Inspect选中的区域。

HTML面板下方的“编辑”按钮可以用于直接编辑选中的HTML代码,而后面显示的是当前元素在整个DOM中的结构路径。

3.查看DOM中被脚本更改的部分

通过JavaScript来改变样式属性的值可以完成一些动画效果。例如提供例子中的暗箱操作例子,打开页面后,可以利用查看(Inspect)功能来选择相应的HTML代码。例如选中例子的中间区域,如图B-14所示。

[插图]

图B-14 选择页面中的元素

单击代码前面的“+”号,可以将代码展开。展开代码如图B-15所示。

[插图]

图B-15 展开的代码

当第1次单击向右的按钮后,会出现图B-16所示的效果。

[插图]

图B-16 单击向右按钮后

通过上图可以看出,HTML 查看器会将页面上改变的内容也记录下来,并以黄色高亮标记。有了这个功能,网页的暗箱操作将彻底成为历史。笔者经常利用该功能查看其他网站的动画效果是如何实现的。

4.查看和修改元素的样式

在右侧的样式面板中,展示了此元素当前所有的样式。所有样式都可以实时地禁用以及修改,如图B-17所示。通过在“padding:10px 8px”前单击就可以禁用此规则,通过直接在样式上value值上单击就可以修改。

[插图]

图B-17 在“样式”面板中可以禁用、修改和添加样式

单击“布局”面板即可看到此元素具体的布局属性,这是一个标准的盒模型。通过“布局”面板,可以很容易地看到元素的偏移量、外边距、边框、内边距和元素的高度、宽度等信息,如图B-18所示。

[插图]

图B-18 在“布局”面板中可以查看某元素具体的布局属性

5.查看DOM的信息

单击“DOM”面板后可以看到此元素的详细的DOM信息以及函数和事件,如图B-19所示。

[插图]

图B-19 在“DOM”面板中可以查看某元素的详细DOM信息

B.2.3 CSS、DOM和网络面板

这3个面板相对于前面2个面板比较次要,CSS和DOM面板与HTML面板中右侧的面板功能相似,但不如HTML面板灵活,因此一般使用得很少。

CSS 面板特有的一个功能就是可以分别详细查看页面中内嵌以及动态导入的样式。例如firebug6.html,可以在Firebug中看到图B-20所示的效果。

[插图]

图B-20 在CSS面板中选择样式

单击CSS面板后就可以分别查看相应的样式。此处展示的样式都是经过格式化的,适合于学习CSS的代码格式和规范。

而在网络面板中,相对有一些强大的功能。例如打开Google网站中国首页,Firebug显示效果如图B-21所示。

[插图]

图B-21 网络面板概览

该页面可以监视每一项元素的加载情况,包括脚本,图片等的大小以及加载用时等,对于页面优化有着极其重要的意义。

此外顶部还可以分类查看元素的HTML、CSS、JS等的加载情况,使分析更加灵活。

B.2.4 脚本面板

脚本面板不仅可以查看页面内的脚本,而且还有强大的调试功能。

在脚本面板的右侧有“监控”、“堆栈”和“断点”3个面板,利用Firebug提供的设置断点的功能,可以很方便的调试程序。如图B-22所示。

[插图]

图B-22 脚本面板右侧的3个面板

1.静态断点

例如firebug7.html,其HTML代码如下:

[插图]

运行代码后可以看到图B-23所示的效果。图中加粗并有颜色的行号表示此处为JavaScript代码,可以在此处设置断点。

[插图]

图B-23 脚本面板

比如,我们在第7行这句代码前面单击一下,那么它前面就会出现一个红褐色的原点,表示此处已经被设置了断点。此时,在右侧断点面板中的断点列表中,就出现了刚才设置的断点。如果想暂时禁用某个断点,可以在断点列表中去掉某个断点前面的复选框里的钩,那么此时左侧面板中相应的断点就从红褐色变成了红灰褐色。如图B-24所示。

[插图]

图B-24 设置断点

设置完断点后,我们就可以开始调试程序了。单击页面中的“Click Me!”按钮,可以看到脚本停止在用淡黄色底色标出的那一行上。此时用鼠标移动到某个变量上即可显示此时这个变量的值。显示效果如图B-25所示。

[插图]

图B-25 程序停在断点上

此时JavaScript内容上方的[插图]这4个按钮已经变得可用了。它们分别代表“继续执行”、“单步进入”、“单步跳过”和“单步退出”。

  • 继续执行<F8>:当通过断点来停止执行脚本时,单击<F8>则会恢复执行脚本。
  • 单步进入<F11>:允许跳到页面中的其他函数内部。
  • 单步跳过<F10>:单击<F10>来直接跳过函数的调用即跳到return之后。
  • 单步退出<Shift+F11>:允许恢复脚本的执行,直到下一个断点为止。

单击第2个按钮“单步进入”,代码会跳到下一行。显示效果如图B-26所示。

[插图]

图B-26 单步进入

从上图可以看到,当鼠标移动到变量“lbl”上时,就可以显示出它的内容是一个DOM元素即“div# messageLabel”。

此时将右侧面板切换到“监控”面板,这里列出了几个变量,包括“this”的指向以及“lbl”的变量。单击“+”就可以看到详细信息。显示效果如图B-27所示。

[插图]

图B-27 “监控”面板

2.条件断点

例如firebug8.html,其HTML代码如下:

[插图]

在“lbl.innerHTML+=arr+”
“”这行代码前面的序号上单击鼠标右键,就可以出现设置条件断点的输入框。在该框内输入“arr==5”,然后按回车键确认,显示效果如图B-28所示。

[插图]

图B-28 设置条件断点

最后单击页面中的“Click Me!”按钮。可以发现,脚本在“arr==5”这个表达式为真时停下了,因此“5”以及之后的数字没有显示到页面中。图B-29和B-30是正常效果和设置条件断点后的效果的对比。

[插图]

图B-29 正常效果

[插图]

图B-30 设置条件断点后的效果

B.3 一些资源

1.快捷键

<F12>键可以快速开启 Firebug,如果想要获取完整的快捷键列表,可以访问 http://getfirebug.com/wiki/index.php/Keyboard_and_Mouse_Shortcuts

2.问题

如果安装过程中遇到了困难,可以查看Firebug的Q&A,网址为http://getfirebug.com/wiki/index.php/FAQ

3.Firebug插件

Firebug除了本身强大的功能之外,还有基于Firebug的插件,它们用于扩充Firebug的功能。比如Google公司开发Page Speed插件,开发人员可以使用它来评估他们网页的性能,并获得有关如何改进性能的建议。Yahoo公司开发的用于检测页面整体性能的YSlow和用于调试PHP的FirePHP。还有用于调试Cookie的Firecookie等。

B.4 总结

通过本节的学习,读者可以掌握Firebug的基本功能,并且能对以后的学习和工作提供一定的帮助。Firebug已经逐渐成为一个调试平台,而不仅仅是一个简单的Firefox的扩展插件。

附录C Ajax的XMLHttpRequest对象的属性和方法

XMLHttpRequest对象是Ajax的核心,它有许多的属性、方法和事件以便于脚本处理和控制HTTP的请求与响应。下面是关于XMLHttpRequest对象的一些属性和方法的介绍。

1.readyState属性

当一个XMLHttpRequest对象被创建后,readyState属性标识了当前对象正处于什么状态,可以通过对该属性的访问,来判断此次请求的状态然后做出相应的操作。readyState属性具体的值代表的意义如表C-1所示。

表C-1 readyState属性

[插图]

2.responseText属性

responseText属性包含客户端接收到的HTTP响应的文本内容。当readyState属性值为0、1或2时,responseText属性包含一个空字符串;当readyState属性值为3(正在接收)时,响应中包含客户端还未完成的响应信息;当readyState属性值为4(已加载)时,该responseText属性才包含完整的响应信息。

3.responseXML属性

只有当readyState属性值为4,并且响应头部的Content-Type的MIME类型被指定为XML(text/xml或者application/xml)时,该属性才会有值并且被解析为一个XML文档,否则该属性值为null。如果是回传的XML文档结构不良或者未完成响应回传,该属性值也会为null。由此可见,responseXML属性用来描述被XMLHttpRequest解析后的XML文档的属性。

4.status属性

status属性描述了HTTP状态代码。注意,仅当readyState属性值为3(正在接收中)或4(已加载)时,才能对此属性进行访问。如果在readyState属性值小于3时,试图存取status属性值,将引发一个异常。

5.statusText属性

statusText属性描述了HTTP状态代码文本,并且仅当readyState属性值为3或4时才可用。当readyState属性为其他值时试图存取statusText属性值将引发一个异常。

6.onreadystatechange事件

每当 readyState 属性值发生改变时,就会触发 onreadystatechange 事件。一般都通过该事件来触发回传处理函数。

7.open()方法

XMLHttpRequest对象是通过调用open(method,uri,async,username,password)方法来进行初始化工作的。调用该方法将得到一个可以用来进行发送(send()方法)的对象。open()方法有5个参数。
(1)method参数是必须提供的,用于指定用来发送请求的HTTP方法(GET,POST,PUT, DELETE或HEAD)。按照HTTP规范,该参数要大写。
(2)uri参数用于指定XMLHttpRequest对象把请求发送到的服务器相应的URI,该地址会被自动解析为绝对地址。
(3)async参数用于指定是否请求是异步的,其默认值为true。如果需要发送一个同步请求,需要把该参数设置为false。
(4)如果需要服务器验证访问用户的情况,那么可以设置username以及password这两个参数。

8.send()方法

调用open()方法后,就可以通过调用send()方法按照open()方法设定的参数将请求进行发送。当open()方法中async参数为true时,在send()方法调用后立即返回,否则将会中断直到请求返回。需要注意的是,send()方法必须在readyState属性值为1时,即调用open()方法以后才能调用。在调用send()方法以后到接收到响应信息之前,readyState 属性的值将被设为2;一旦接收到响应消息, readyState属性值将会被设为3;直到响应接收完成,readyState属性的值才会被设为4。

send()方法使用一个可选的参数,该参数可以包含可变类型的数据。用户可以使用它并通过POST方法把数据发送到服务器。另外,可以显式地使用null参数调用send()方法,这与不用参数调用该方法一样。对于大多数其他的数据类型,在调用send()方法之前,应该使用setRequestHeader()方法先设置Content-Type头部。如果send(data)方法中的data参数的类型为DOMString,那么,数据将被编码为UTF-8。如果数据是Document类型,那么将使用由data.xmlEncoding指定的编码串行化该数据。

9.abort()方法

该方法可以暂停一个HttpRequest的请求发送或者HttpResponse的接收,并且将XMLHttp Request对象设置为初始化状态。

10.setRequestHeader()方法

该方法用来设置请求的头部信息。当readyState属性值为1时,可以在调用open()方法后调用这个方法;否则将得到一个异常。setRequestHeader(header,value)方法包含两个参数:前一个是header键名称,后一个是键值。

11.getResponseHeader()方法

此方法用于检索响应的头部值,仅当readyState属性值是3或4(即在响应头部可用以后)时,才可以调用这个方法;否则,该方法返回一个空字符串。此外还可以通过getAllResponse Headers()方法获取所有的HttpResponse的头部信息。

附录E jQuery加载并解析XML

E.1 简述

XML(eXtensible Markup Language)即可扩展标记语言,与HTML一样,都是属于SGML标准通用语言。在XML中,采用了如下语法。
(1)任何起始标签都必须有一个结束标签。
(2)可以采用另一种简化语法,即在一个标签中同时表示起始和结束标签。这种语法是在右边闭合尖括号之前紧跟一个斜线(/),例如<tag/>。XML解析器会将其翻译成<tag></tag>
(3)标签必须按照合理的顺序进行嵌套,因此结束标签必须按镜像顺序匹配起始标签,例如<b> this is a <i>sample</i> string</b>。这相当于将起始和结束标签看作是数学中的左右括号,在没有关闭所有的内部括号之前,不能关闭外面的括号。
(4)所有的属性都需要有值,并且需要在值的周围加上双引号。

E.2 Content-Type

很多情况下XML文件不能正常解析都是由于Content-Type没有设置好。如果Content- Type本身就是一个XML文件则不需要设置;如果是由后台程序动态生成的,那么就需要设置Content-Type为“text/xml”,否则jQuery会以默认的“text/html”方式处理,导致解析失败。以下是几种常见语言中设置Content-Type的方式。

[插图]

E.3 XML结构

作为一个标准的XML,必须要遵循严格的格式规定,其中最重要的一条规则就是XML必须是封闭的。例如如下代码就是错误的,因为它并没有闭合。

[插图]

另外XML文档只能有一个顶层元素。例如以下代码就是错误的,原因是它有多个顶层元素。

[插图]

一个正确的XML应该是下面这样的形式。

[插图]

E.4 获取XML

利用上面提到的正确的XML,通过jQuery的Ajax函数进行读取,jQuery代码如下:

[插图]

这样就可以很容易地从后台读取到一段XML,当然也可以用简单的$.get()方法和$.post()方法来去获取。代码如下:

[插图]

E.5 解析XML

解析XML文档与解析DOM一样,也可以用find()、children()等函数来解析和用each()方法来进行遍历,另外也可以用text()和attr()方法来获取节点文本和属性。例如在success回调里解析XML。代码如下:

[插图]

通过上面的代码,能成功获取到相应的数据。接下来就可以将解析出来的数据添加到已有的HTML文件中。通常可以先生成一个DOM元素片段,然后将数据用appendTo()函数添加进这个元素片段中,最后将这个片段添加进HTML文档中。success回调代码如下:

[插图]

E.6 禁用缓存

在项目中经常会遇到一个问题,即数据已经更新了,但传递的还是以前的数据。要避免这种情况,就应当禁用缓存。禁用缓存的方式有很多种。如果是通过$.post()方法获取的数据,那么默认就是禁用缓存的。如果是用了$.get()方法,可以通过设置时间戳来避免缓存。可以在 URL的后面加上+(+new Date),代码如下:

[插图]

之所以不用随机数,是因为随机数对于同一台电脑来说,在大量使用之后出现重复的概率会很大,而用时间戳则不会出现这种情况。

此外,如果使用了$.ajax()方法来获取数据,只需要设置cache:false即可。但要注意,false是布尔值而不是一个字符串,在这一点上初学者很容易犯错。

掌握了以上内容后,读者就可以顺利地写出符合XML语法规范并能正确解析的XML文件了。

附录F 插件API

F.1 Validation插件API

Validation插件有两个经常被用到的选项,分别是方法(method)和规则(rule)。
(1)方法。验证方法就是通过执行验证逻辑判断一个元素是否合法。例如 email()方法就是检查当前文本格式是否是正确的E-mail格式。读者能很方便地利用Validation插件提供的方法来完成验证。另外,读者也可以自定义方法。
(2)规则。验证规则将元素和元素的验证方法关联起来,例如验证一个需要E-mail格式和必填的属性name为email的元素,可以定义该元素的规则如下:

[插图]

  • 插件方法如表F-1所示。
表F-1 插件方法

[插图]

  • 内置验证规则如表F-2所示。
表F-2 内置验证规则

[插图]

  • Validator

Validation验证会返回一个Validator对象,validator对象可以帮助用户触发validation程序或者改变form的内容。validator对象更多的方法如表F-3所示。

表F-3 validator对象的方法

[插图]

validator对象中的静态方法如表F-4所示。

表F-4 validator对象中的静态方法

[插图]

  • 实用项
表F-5 实用项

[插图]

  • 普通选择器
表F-6 普通选择器

[插图]

F.2 Form插件API

(1)Form插件API

Form插件拥有很多方法,这些方法可以使用户很容易地管理表单数据和提交表单。

  • ajaxForm()

增加所需要的事件监听器,为Ajax 提交表单做好准备。AjaxForm()方法并没有提交表单,而是在$(document).ready()方法中,使用ajaxForm()方法来为Ajax提交表单做好准备。ajaxForm方法可以接受0个或1个参数。单个的参数既可以是一个回调函数,也可以是一个Options对象。此方法可以进行链式操作。

例子:

[插图]

  • ajaxSubmit()

立即通过Ajax方式提交表单。在大多数情况下,都是调用ajaxSubmit()方法来响应用户的提交表单操作。AjaxSubmit()方法可以接受0个或1个参数。单个的参数既可以是一个回调函数,也可以是一个Options对象。此方法可以进行链式操作。

例子:

[插图]

  • formSerialize()

该方法将表单中所有的元素串行化(序列化)为一个字符串。formSerialize()方法会返回一个格式化好的字符串,格式如下:

[插图]

因为返回的是字符串,而不是jQuery对象,所以该方法不能进行链式操作。

例子:

[插图]

  • fieldSerialize()

fieldSerialize()方法将表单的字段元素串行化(序列化)成一个字符串。当用户只需要串行化表单的一部分时就可以用到该方法了。fieldSerialize()方法会返回一个格式化后的字符串,格式如下:

[插图]

因为返回的是字符串,所以该方法不可以进行链式操作。

例子:

[插图]

  • fieldValue()

fieldValue()方法把匹配元素的值插入到数组中,然后返回这个数组。从0.91版本起,该方法总是以数组的形式返回数据,如果元素值被判定无效,则数组为空,否则数组将包含一个或多个元素值。fieldValue()方法返回一个数组,因此不可以进行链式操作。

例子:

[插图]

  • resetForm()

该方法通过调用表单元素原有的DOM 方法重置表单到初始状态。resetForm()方法可以进行链式操作。

例子:

[插图]

  • clearForm()

clearForm()方法用来清空表单中的元素。该方法将所有的文本框(text)、密码框(password)和文本域(textarea)元素置空,清除下拉框(select)元素的选定以及将所有的单选按钮(radio)和多选按钮(checkbox)重置为非选定状态。clearForm()方法可以进行链式操作。

例子:

[插图]

  • clearFields()

clearFields()方法用来清空字段元素。当用户需要清空一部分表单元素时就会用到该方法。clearFields()方法可以进行链式操作。

例子:

[插图]

(2)ajaxForm and ajaxSubmit的Options对象

ajaxForm()方法和ajaxSubmit()方法支持许多选项,这些选项都可以通过Options对象来设置。Options对象是一个简单的JavaScript对象,包含了如下属性与值的集合。

  • target

指明页面中根据服务器响应进行更新的元素。这个值可能是一个特殊的jQuery选择器字符串、一个jQuery对象或者一个DOM元素。

默认值:null。

  • url

将表单元素提交到指定的url中。

默认值:表单action属性的值。

  • type

指定提交表单数据的方法(method):GET或POST。

默认值:表单method属性的值(如果没有找到,则为GET)。

  • beforeSubmit

表单提交前的回调函数。beforeSubmit回调函数被用来运行预提交逻辑或者校验表单数据。假如beforeSubmit回调函数返回false,则表单将不会被提交。beforeSubmit回调函数有3个参数:数组形式的表单数据、jQuery表单对象和传递给ajaxForm()方法或ajaxSubmit()方法的Options对象。表单数据数组遵循以下数据格式(json类型)。

[插图]

默认值:null。

  • success

表单成功提交后调用的回调函数。假如success回调函数被指定,将在服务器返回响应后被调用。success函数可以传回responseText或者responseXML的值(决定值的数据类型是dataType选项)

默认值:null。

  • dataType

期望的服务器响应的数据类型,可以是null、xml、script或者json。dataType提供了指定的方法以便控制服务器的响应。这个指定的方法将被直接地反映到jQuery.httpData()方法中。dataType支持以下格式。

  • xml。如果dataType被指定为xml,服务器返回内容将被作为XML来对待。同时,如果“success”回调函数被指定,responseXML的值将会传递给回调函数。
  • json。如果dataType被指定为json,服务器返回内容将被执行,如果“success”回调函数被指定,返回的内容将会传递给回调函数。
  • script。如果dataType被指定为script,服务器返回内容将被放在全局环境中执行。

默认值:null。

  • semantic

是否需要定义为严格的语义格式。注意,普通的表单序列化要遵循的语义不能包括type 属性为image的input元素。假如服务器有严格的语义要求,而表单也至少包含一个type=”image”元素的时候,那么必须设置semantic选项为true。

默认值:false。

  • resetForm

表单是否在提交成功后被重置。

默认值:null。

  • clearForm

表单是否在提交成功后被清空。

默认值:null。

  • iframe

表单是否总是将服务器响应指向到一个iframe。iframe在文件上传时会很有用。

默认值:false。

  • data

包含额外数据的对象通过form形式提交。

[插图]

  • error

错误时的回调函数。

  • beforeSerialize

回调函数被调用前被序列化。它可以在调用之前检索其值的形式。它带有两个参数:form对象和ajaxForm/ ajaxSubmit传递过来的options对象。

[插图]

默认值:null。

  • replaceTarget

可选,与target选项一起使用。如果想将目标元素一起替换掉,请设为true,如果只想替换目标元素的内容,则设为false。

默认值:false。在v2.43后增加。

  • iframeSrc

字符串值,当/如果使用iframe时作为iframe的src属性。

默认值:about:blank

网页使用https协议时默认值为:javascript:false

  • forceSync

布尔值,当上传文件(或使用iframe选项)时,提交表单前为了消除短延迟,设置为true。延迟的使用是为了让浏览器渲染DOM更新前执行原有的表单submit。这时显示一条信息告知用户,如:“请稍等…”,会改善可用性。

默认值:false。在v2.38后增加。

  • uploadProgress

上传进度信息(如果浏览器支持)回调函数。回调传递以下参数:
1)event:浏览器事件
2)position:位置(整数)
3)total:总长度(整数)
4)percentComplete:完成度(整数)

默认值:null

  • iframeTarget

使用iframe元素作为响应文件上传目标。默认情况下,该插件将创建一个临时的iframe元素来捕捉上传文件时的反应。此选项允许您使用现有的iframe,如果你想。使用此选项时,插件对来自服务器的响应不作任何处理。

默认值:null。在v2.76后增加。

(3)Form插件实例

[插图]

注意,利用此Options对象,可以将值传给jQuery的$.ajax()方法。假如用户熟悉$.ajax()方法提供的options对象,那么可以利用它们来将Options对象传递给ajaxForm()方法和ajaxSubmit()方法。

F.3 SimpleModal插件API

API的官方网站地址为:http://www.ericmmartin.com/projects/simplemodal/

表F-7 SimpleModal插件的API

[插图]

F.4 Cookie插件API

API的官方网站地址为:https://github.com/carhartl/jquery-cookie

  • 写入Cookie。

[插图]

注意:“the_cookie”为待写入的Cookie名,“the_value”为待写入的值。
  • 删除Cookie。

[插图]

注意:“the_cookie”为Cookie名,设置为null即删除此Cookie。必须使用与之前设置时相同的路径(path)和域名(domain),才可以正确删除Cookie。
  • 其他可选参数。

[插图]

注意: - expires:(Number|Date)有效期。可以设置一个整数作为有效期(单位:天),也可以直接设置一个日期对象作为Cookie的过期日期。如果指定日期为负数,例如已经过去的日子,那么此Cookie将被删除;如果不设置或者设置为null,那么此Cookie将被当作Session Cookie处理,并且在浏览器关闭后删除。 - path:(String)cookie的路径属性。默认是创建该Cookie的页面路径。 - domain:(String)cookie的域名属性。默认是创建该Cookie的页面域名。 - secure:(Boolean)如果设为true,那么此Cookie的传输会要求一个安全协议,例如HTTPS。

附录G jQuery速查表

G.1基础

[插图]

G.2 选择器

[插图]

续表

[插图]

G.3属性

[插图]

G.4筛选

[插图]

G.5文档处理

[插图]

G.6CSS

[插图]

G.7事件

[插图]

续表

[插图]

续表

[插图]

G.8效果

[插图]

G.9Ajax

[插图]

G.10实用项

[插图]

G.11其他对象

[插图]

续表

[插图]

第10章 理解反应式编程

本章内容:
  • 反应式编程概览
  • Reactor项目简介
  • 反应式地处理数据

你曾有过订阅报纸或者杂志的经历吗?互联网的确从传统的出版发行商那儿分得了一杯羹,但是过去订阅报纸真的是我们了解时事的最佳方式。那时,我们每天早上都会收到一份新鲜出炉的报纸,并在早饭时间或上班路上阅读。

现在假设一下,在支付完订阅费用之后,几天的时间过去了,你却没有收到任何报纸。又过了几天,你打电话给报社的销售部门询问为什么还没有收到报纸。想象一下,如果他们告诉你:“因为你支付的是一整年的订阅费用,而现在这一年还没有结束,当这一年结束时,你肯定可以一次性完整地收到它们。”那么你会有多么惊讶。

值得庆幸的是,这并非订阅的真正运作方式。报纸具有一定的时效性。在出版后,报纸需要及时投递,以确保在阅读它们时内容仍然是新鲜的。此外,当你在阅读最新一期的报纸时,记者们正在为未来的版本撰写内容,同时印刷机正在满速运转,印刷下一期的内容——一切都是并行的。

在开发应用程序代码时,我们可以编写两种风格的代码,即命令式和反应式。

  • 命令式(Imperative)的代码:非常类似于上文所提的虚构的报纸订阅方式。它由一组任务组成,每次只运行一项任务,每项任务又都依赖于前面的任务。数据会按批次进行处理,在前一项任务还没有完成对当前数据批次的处理时,不能将这些数据递交给下一项处理任务。
  • 反应式(Reactive)的代码:非常类似于真实的报纸订阅方式。它定义了一组用来处理数据的任务,但是这些任务可以并行地执行。每项任务处理数据的一部分子集,并将结果交给处理流程中的下一项任务,同时继续处理数据的另一部分子集。

在本章中,我们将暂别Taco Cloud应用程序,转而探索Reactor项目。Reactor是一个反应式编程库,同时也是Spring家族的一部分。它是Spring 5反应式编程功能的基础,所以在我们学习使用Spring构建反应式控制器和repository之前,理解Reactor是非常重要的。不过,在我们开始学习Reactor之前,还需要花点时间研究一下反应式编程的基本要素。

10.1 反应式编程概览

反应式编程是一种可以替代命令式编程的编程范式。这种可替代性存在的原因在于反应式编程解决了命令式编程中的一些限制。理解这些限制,有助于你更好地理解反应式编程模型的优点。

注意:反应式编程不是银弹。你不应该从这一章或者其他任何关于反应式编程的讨论中得出“命令式编程是邪恶的,而反应式编程才是你的救星”的结论。如同我们作为开发者学习到的任何技术一样,反应式编程对于某些使用场景来说的确是完美的,但是在其他的一些场景中可能不那么适用。建议以实用主义为上。

如果你和我以及绝大多数的开发者一样,是从命令式编程开始入行的,那么很可能你现在编写的大部分(或者所有)代码在将来依然是命令式的。命令式编程相当直观,没有编程经验的学生们可以在学校的STEM教育课程中轻松地学习它,而且足够强大。在驱动大型企业运行的代码中,绝大部分都是命令式的。

它的理念很简单:你可以一次一个地按照顺序将代码编写为需要遵循的指令列表。在某项任务开始执行之后,程序在开始下一项任务之前需要等待当前任务完成。在整个处理过程中的每一步,要处理的数据都必须是完全可用的,以便将它们作为一个整体进行处理。

一开始一切都很美好,直到我们遇到问题。在执行一项任务的时候,特别是IO任务(将数据写入DB或者从远程服务器获取数据),触发这项任务的线程实际上是被阻塞的,在任务完成之前它不能做任何事情。坦白来说,阻塞线程是一种浪费。

大多数编程语言(包括Java)都支持并发编程。在Java中创建一个线程,然后让它执行某些操作,而调用线程继续执行其他工作,这是相当容易实现的。虽然创建线程很简单,但是这些线程多半最终会被阻塞。管理多线程中的并发极具挑战,而更多线程则意味着更多的复杂性。

相比之下,反应式编程本质上是函数式的和声明式的。相对于描述一组将依次执行的步骤,反应式编程描述了数据将会流经的管道或者流。相对于要求将被处理的数据作为一个整体进行处理,反应式流可以在数据可用时立即开始处理。实际上,传入的数据可能是无限的(比如,一个某个地理位置的实时温度测量数据的恒定流)。

拿现实世界类比一下,可以将命令式编程看作是水气球,而将反应式编程看作是花园里的软管。在夏天,这两者都是偷袭和愉悦毫无戒心的朋友的好方式,但是它们的运作方式却不同:

  • 水气球只能一次性地填满有效载荷,并在撞到目标时弄湿对象。水气球的容量有限,如果你想要弄湿更多人(或者把同一个人弄得更加湿透一点),那么唯一的选择就是增加水气球的数量。
  • 花园软管的有效载荷是从水龙头到喷嘴的水流。在特定的时间点,花园软管的容量可能是有限的,但是在打水仗的过程中它的容量却是无限的。只要水源源不断地从龙头流入软管中,水就会源源不断地从喷嘴喷出去。同一个软管非常好扩展,你可以尽情地和更多的朋友打水仗。

虽然水气球(或者命令式编程)没有什么固有的问题,但是持有软管(或者能够应用反应式编程)的人通常在伸缩性和性能方面更具优势。

定义反应式流

反应式流(Reactive Streams)是由Netflix、Lightbend和Pivotal(Spring背后的公司)的工程师于2013年年底开始制定的一种规范。反应式流旨在提供无阻塞回压的异步流处理标准。

我们已经触及了反应式编程的异步特性,它使我们能够并行执行任务,从而实现更高的可伸缩性。通过回压,数据消费者可以限制它们想要处理的数据数量,避免被过快的数据源所淹没。

Java的流和反应式流

Java的流和反应式流之间有很多相似之处。首先,它们的名字中都有流(Stream)这个词。它们还提供了用于处理数据的函数式API。事实上,正如你稍后将会在我们介绍Reactor时看到的那样,它们甚至可以共享许多相同的操作。

Java的流通常都是同步的,并且只能处理有限的数据集。从本质上来说,它们只是使用函数来对集合进行迭代的一种方式。

反应式流支持异步处理任意大小的数据集,同样也包括无限数据集。只要数据就绪,它们就能实时地处理数据,并且能够通过回压来避免压垮数据的消费者。

反应式流规范可以总结为4个接口:Publisher、Subscriber、Subscription和Processor。Publisher负责生成数据,并将数据发送给Subscription(每个Subscriber对应一个Subscription)。Publisher接口声明了一个方法subscribe(),Subscriber可以通过该方法向Publisher发起订阅。

反应式流规范可以总结为4个接口:Publisher、Subscriber、Subscription和Processor。Publisher负责生成数据,并将数据发送给Subscription(每个Subscriber对应一个Subscription)。Publisher接口声明了一个方法subscribe(),Subscriber可以通过该方法向Publisher发起订阅。

1
2
3
public interface Publisher<T> {
void subscribe(Subscriber<? super T> subscriber);
}

一旦Subscriber订阅成功,就可以接收来自Publisher的事件。这些事件是通过Subscriber接口上的方法发送的:

1
2
3
4
5
6
public interface Subscriber<T> {
void onSubscribe(Subscription sub);
void onNext(T item);
void onError(Throwable ex);
void onComplete();
}

Subscriber的第一个事件是通过对onSubscribe()方法的调用接收的。Publisher调用onSubscribe() 方法时,会将Subscription对象传递给Subscriber。通过Subscription,Subscriber可以管理其订阅情况:

1
2
3
4
public interface Subscription {
void request(long n);
void cancel();
}

Subscriber可以通过调用request()方法来请求Publisher发送数据,或者通过调用cancel()方法表明它不再对数据感兴趣并且取消订阅。当调用request()时,Subscriber可以传入一个long类型的数值以表明它愿意接受多少数据。这也是回压能够发挥作用的地方,以避免Publisher发送多于Subscriber能够处理的数据量。在Publisher发送完所请求数量的数据项之后,Subscriber可以再次调用request()方法来请求更多的数据。

Subscriber请求数据之后,数据就会开始流经反应式流。Publisher发布的每个数据项都会通过调用Subscriber的onNext()方法递交给Subscriber。如果有任何错误,就会调用onError()方法。如果Publisher没有更多的数据,也不会继续产生更多的数据,那么将会调用Subscriber的onComplete()方法来告知Subscriber它已经结束。

至于Processor接口,它是Subscriber和Publisher的组合,如下所示:

1
2
public interface Processor<T, R>
extends Subscriber<T>, Publisher<R> {}

当作为Subscriber时,Processor会接收数据并以某种方式对数据进行处理。然后它会将角色转变为Publisher,并将处理的结果发布给它的Subscriber。

正如你所看到的,反应式流的规范非常简单,很容易就能想出如何构建一个以Publisher作为开始的数据处理管道,并让数据通过零个或多个Processor,然后将最终结果投递给Subscriber。

然而,反应式流规范的接口本身并不支持以函数式的方式组成这样的流。Reactor项目是反应式流规范的一个实现,提供了一组用于组装反应式流的函数式API。我们将会在后面的内容中看到,Reactor构成了Spring 5反应式编程模型的基础。在本章的其余部分,我们将会探讨(并且,我敢说这个过程非常有意思)Reactor项目。

10.2 初识Reactor

反应式编程要求我们采取和命令式编程不一样的思维方式。此时我们不会再描述每一步要进行的步骤,反应式编程意味着要构建数据将要流经的管道。当数据流经管道时,可以对它们进行某种形式的修改或者使用。

例如,假设我们想要接受一个英文人名,然后将所有的字母都转换为大写,并用得到的结果创建一个问候消息,并最终打印它。使用命令式编程模型,代码看起来如下所示:

1
2
3
4
String name = "Craig";
String capitalName = name.toUpperCase();
String greeting = "Hello, " + capitalName + "!";
System.out.println(greeting);

使用命令式编程模型,每行代码执行一个步骤,按部就班,并且肯定在同一个线程中进行。每一步在执行完成之前都会阻止执行线程执行下一步。

与之不同,如下的函数式、反应式代码完成了相同的事情:

1
2
3
4
5
6
7
8
9

String name = "Craig";
String capitalName = name.toUpperCase();
String greeting = "Hello, " + capitalName + "!";
System.out.println(greeting);
Mono.just("Craig")
.map(n -> n.toUpperCase())
.map(cn -> "Hello, " + cn + "!")
.subscribe(System.out::println);

不用过度关心这个例子中的细节,我们很快将会详细讨论just()、map()和subscribe() 方法。现在,重要的是要理解:虽然这个反应式的例子看起来依然保持着按步骤执行的模型,但实际是数据会流经处理管线。在处理管线的每一步,都对数据进行了某种形式的加工,但是我们不能判断数据会在哪个线程上执行操作。它们既可能在同一个线程,也可能在不同的线程。

这个例子中的Mono是Reactor的两种核心类型之一,另一个类型是Flux。两者都实现了反应式流的Publisher接口。Flux代表具有零个、一个或者多个(可能是无限个)数据项的管道。Mono是一种特殊的反应式类型,针对数据项不超过一个的场景,它进行了优化。

Reactor与RxJava(ReactiveX)的对比

如果你熟悉RxJava或者ReactiveX,那么你可能认为Mono和Flux类似于Observable和Single。事实上它们不仅在语义上大致相同,还共享了很多相同的操作符。

虽然我们在本书中主要介绍Reactor,但是Reactor和RxJava的类型可以互相转换,我相信你对这一点会感到很开心。甚至,在接下来的章节中我们还会看到,Spring也可以使用RxJava的类型。

实际上,在前面的例子中有3个Mono。其中,just() 操作创建了第一个Mono。当该Mono发送一个值的时候,这个值被传递给了将字母转换为大写的map()操作,据此又创建了另一个Mono。当第二个Mono发布它的数据时,数据被传递给了第二个map()操作,并且会在此进行一些字符串连接操作,而结果将用于创建第三个Mono。最后,对第三个Mono上的subscribe()方法调用时,会接收数据并将数据打印出来。

10.2.1 绘制反应式流图

反应式流程通常使用弹珠图(marble diagram)表示,如图10.1所示。弹珠图的展现形式非常简单,在顶部描述了数据流经Flux或者Mono的时间线,在中间描述了要执行的操作,在底部描述了结果形成的Flux或者Mono的时间线。我们将会看到,当数据流经原始的Flux时,某些操作将会对它进行处理,并产生一个新的Flux,已经处理过的数据将会在新Flux中流动。

image-20211016111138194

图10.1 描绘Flux基本流程的弹珠图

图10.2展示了一个类似的弹珠图,但是针对的是Mono。我们可以看到,这里主要的不同是Mono将会有零个或者一个数据项,或者一个错误。

image-20211016111229961

图10.2 描绘Mono基本流程的弹珠图

10.2.2 添加Reactor依赖

要开始使用Reactor,请将下面的依赖项添加到项目的构建文件中:

1
2
3
4
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>

Reactor还提供了非常棒的测试支持。我们将会围绕Reactor代码编写大量的测试,因此绝对需要将下面的依赖添加到构建文件中:

1
2
3
4
5
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>

如果你计划将这些依赖添加到一个Spring Boot工程中,那么Spring Boot工程会替你管理依赖。但是,如果要在非Spring Boot项目中使用Reactor,就需要在构建文件中设置Reactor的BOM(Bill Of Materials,物料清单)。下面的依赖管理条目将会把Reactor的Bismuth版本添加到构建文件中:

1
2
3
4
5
6
7
8
9
10
11
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>Bismuth-RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

现在Reactor已经位于工程的构建文件中了,我们可以开始使用Mono和Flux来创建反应式的处理管线。在本章的剩余部分,我们将介绍Mono和Flux所提供的几个操作。