Ajax, 全称Asynchronous JavaScript + XML, 是一种能够像服务器请求额外的数据而无需卸载页面的技术.
Ajax技术的核心是XMLHttpRequest对象(简称XHR). XHR为向服务器发送请求和解析服务器响应提供了流畅的接口. 能够以异步方式从服务器取得更多信息, 意味着用户单击后, 可以不必刷新页面也能取得新数据. 也就是说, 可以使用 XHR对象取得新数据, 然后再通过DOM将新数据插入到页面中.
另外, 虽然名字中包含XML的成分, 但Ajax通信与数据格式无关, 这种技术就是无须刷新页面即可从服务器取得数据, 但不一定是XML数据.
1、创建XHR对象
IE7+、Firefox、Opera、Chrome和Safari都支持原生的XHR对象, 在这个浏览器中创建XHR对象要像下面这样使用XMLHttpRequest构造函数:
1 | var xhr = new XMLHttpRequest(); |
如果需要支持IE的早期版本, 那么就需要专门为此编写一个函数:
1 | function createXHR(){ |
这个函数会首先检测原生XHR对象是否存在, 如果存在则返回它的新实例. 如果原生对象不存在, 则检测ActiveX对象. 如果这两种对象都不存在, 就抛出一个错误.
然后, 就可以使用以下代码在所有浏览器中创建XHR对象了:
1 | var xhr = createXHR(); |
2、XHR的用法
2.1 open()
在使用XHR对象时, 要调用的第一个方法是open()
, 它接受3个参数:
- 要发送的请求的类型(“get”、”post”等)
- 请求的URL
- 表示是否异步发送请求的布尔值
例如:
1 | xhr.open("get", "example.php", false); |
这行代码会启动一个针对example.php的GET请求. 需要说明两点: 一是URL相对于执行代码的当前页面(或者使用绝对路径); 二是调用open()方法并不会真正发送请求, 而只是启动一个请求以备发送.
注意, 只能向同一个域中使用相同端口和协议的URL发送请求. 如果URL与启动请求的页面有任何差别, 都会引发安全错误.
2.2 send()
要发送特定的请求, 必须像下面这样调用send()
方法:
1 | xhr.open("get", "example.txt", false); |
send()方法接收一个参数, 即要作为请求主体发送的数据. 如果不需要通过请求主体发送数据, 则必须传入 null, 因为这个参数对有些浏览器来说是必需的.
调用send()之后, 请求就会被分派到服务器.
由于这次请求是同步的(第三个参数为false), JavaScript代码会等到服务器响应之后再继续执行.
在收到响应后, 响应的数据会自动填充XHR对象的属性, 相关的属性简介如下:
名称 | 描述 |
---|---|
responseText | 作为响应主体被返回的文本 |
responseXML | 如果响应的内容类型是”text/xml”或”application/xml”,这个属性中将保存包含着响应数据的XML DOM文档 |
status | 响应的HTTP状态 |
statusText | HTTP状态的说明 |
在接收到响应后, 第一步是检查status
属性, 以确定响应已经成功返回.
一般情况下, 可以将HTTP状态代码为200作为成功的标志. 此时, responseText属性的内容已经就绪, 而且在内容类型正确的情况下, responseXML也应该能够访问了. 此外, 状态代码为304表示请求的资源并没有被修改, 可以直接使用浏览器中缓存的版本; 当前, 也意味着响应是有效的.
为确保接收到适当的响应, 应该同时检查上述两种状态代码:
1 | xhr.open("get", "example.txt", false); |
建议最好是通过status来决定下一步的操作, 不要依赖statusText, 因为后者在跨浏览器使用时不太可靠.
另外, 无论内容类型是什么, 响应主体的内容都会保存到responseText属性中; 而对于非XML数据而言, responseText属性的值为null.
2.3 readyState
像前面这样发送同步请求当然没问题, 但多数情况下, 我们还是要发送异步请求, 才能让JavaScript继续执行而不必等待响应. 此时可以检测 XHR对象的readyState属性, 该属性表示请求/响应过程的当前活动阶段.
这个属性可取的值如下:
- 0: 未初始化. 尚未调用open()方法
- 1: 已启动. 已经调用open()方法, 但尚未调用send()方法
- 2: 发送. 已经调用send()方法, 但尚未接收到响应
- 3: 接收. 已经接收到部分响应数据
- 4: 完成. 已经接收到全部响应数据, 而且已经可以在客户端使用了
只要readyState属性的值由一个变为另一个值, 都会触发一次readystatechange事件. 可以利用这个事件来检测每次变化后 readyState的值.
通常, 我们只对readyState值为4的阶段感兴趣, 因为这时所有数据都已就绪.
不过, 必须在调用open()之前指定onreadystatechange事件处理程序才能确保跨浏览器的兼容性.
1 | var xhr = createXHR(); |
以上代码利用DOM 0级方法为XHR对象添加了事件处理程序, 原因是并非所有浏览器都支持DOM 2级方法.
与其他事件处理程序不同, 这里没有向onreadystatechange事件处理程序中传递event对象; 必须通过 XHR对象本身来确定下一步该怎么做.
2.4 abort()
在接收响应之前还可以调用abort()方法来取消异步请求.
1 | xhr.abort(); |
调用这个方法后, XHR对象会停止触发事件, 而且也不再允许访问任何与响应有关的对象属性.
在终止请求之后, 还应该对XHR对象进行解引用操作. 由于内存原因, 不建议重用XHR对象.
3.HTTP头部信息
每个HTTP请求和响应都会带有相应的头部信息, XHR对象提供了操作两种头部(请求头部和响应头部)信息的方法.
默认情况下, 在发送XHR请求的同时, 还会发送下列头部信息:
名称 | 描述 |
---|---|
Accept | 浏览器能够处理的内容类型 |
Accept-Charset | 浏览器能够显示的字符集 |
Accept-Encoding | 浏览器能够处理的压缩编码 |
Accept-Language | 浏览器当前设置的语言 |
Connection | 浏览器与服务器之间连接的类型 |
Cookie | 当前页面设置的任何Cookie |
Host | 发出请求的页面所在的域 |
Referer | 发出请求的页面的URI. |
User-Agent | 浏览器的用户代理字符串 |
以上列出的基本上是所有浏览器都会发送的头部信息.
使用setRequestHeader()方法可以设置自定义的请求头部信息. 该方法接受两个参数: 头部字段的名称和头部字段的值.
要成功发送请求头部信息, 必须在调用open()方法之后且调用send()方法之前调用setRequestHeader(), 如下面的例子所示:
1 | var xhr = createXHR(); |
服务器在接收到自定义头部信息后, 可以执行相应的后续操作. 建议使用自定义的头部字段名称, 不要使用浏览器正常发送的字段名称, 否则可能会影响服务器的响应. 有的浏览器允许开发人员重写默认的头部信息, 但有的浏览器则不允许这样做.
调用XHR对象的getResponseHeader()方法并传入头部字段名称, 可以取得相应的响应头部信息.
而调用getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串.
1 | var myHeader = xhr.getResponseHeader("MyHeader"); |
在服务器端, 也可以利用头部信息向浏览器发送额外的、结构化的数据.
在没有自定义信息的情况下, getAllResponseHeaders()方法通常会返回如下所示多行文本内容:
1 | Date: Sun, 14 Nov 2004 18:04:03 GMT |
这种格式化的输出可以方便我们检查响应中所有头部字段的名称, 而不必一个一个地检查某个字段是否存在.
4.GET请求
GET是最常见的请求类型, 常用于向服务器查询某些信息. 必要时, 可以将查询字符串参数追加到URL的末尾, 以便将信息发送给服务器.
对XHR而言, 位于传入open()方法的URL末尾的查询字符串必须经过正确的编码才行.
使用GET请求经常会发生的一个错误, 就是查询字符串的格式问题. 查询字符串中每个参数的名称和值都必须使用encodeURIComponenet()进行编码, 然后才能放到URL的末尾; 而且所有的名值对都必须由和号(&)分隔.
1 | xhr.open("get", "example.php?name1=value1&name2=value2", true); |
下面这个函数可以辅助向现有URL的末尾添加查询字符串参数:
1 | function addURLParam(url, name, value){ |
这个函数首先检查URL是否包含问号(以确定是否已经有参数存在). 如果没有, 就添加一个问号; 否则就添加一个和号.
然后, 将参数名和值进行编码, 再添加到URL的末尾.
最后返回添加参数之后的URL.
下面是使用这个函数来构建请求URL的示例:
1 | var url = "example.php"; |
5.POST请求
POST请求通常用于向服务器发送应该被保存的数据.
POST请求应该把数据作为请求的主体提交, 而GET请求传统上不是这样.
POST请求的主体可以包含非常多的数据, 而且格式不限.
在open()方法的第一个参数传入”post”就可以初始化一个POST请求.
1 | xhr.open("post", "example.php", true); |
发送POST请求的第二步就是向send()方法中传入某些数据. 可以出传入XML DOM文档, 也可以传入任何想发送到服务器的字符串.
默认情况下, 服务器对POST请求和提交Web表单的请求并不会一视同仁. 因此, 服务器端必须有程序来读取发送过来的原始数据, 并从中解析出有用的部分.
不过, 可以使用XHR来模仿表单提交: 首先将Content-Type头部信息设置为application/x-www-form-urlencoded, 也就是表单提交时的内容类型, 其次是以适当的格式创建一个字符串.
POST数据的格式与查询字符串格式相同, 如果需要将页面中表单的数据进行序列化, 然后再通过XHR发送到服务器, 那么可以使用下面的函数来创建这个字符串:
1 | function serialize(form){ |
这个函数可以将ID为”user-info”的表单中的数据序列化之后发送给服务器.
与GET请求相比, POST请求消耗的资源会更多一些. 从性能角度看, 发送相同的数据, GET请求的速度最多可达到POST请求的两倍.