2005年,Jesse James Garrett发表了一篇在线文章,题为“Ajax: A new Approach to Web Applications”。他在这篇文章里介绍了一种技术,用他的话说,就叫 Ajax,是对 Asynchronous JavaScript + XML 的简写。
Ajax 技术的核心是 XMLHttpRequest 对象(简称 XHR)
21.1 XMLHttpRequest 对象
创建 XHR 对象
var xhr = new XMLHttpRequest();
21.1.1 XHR的用法
在使用 XHR 对象时,要调用的第一个方法是 open(),它接受 3 个参数:要发送的请求的类型 ("get"、"post"等)、请求的 URL 和表示是否异步发送请求的布尔值。
xhr.open("get", "example.php", false);
要发送特定的请求,必须像下面这样调用 send()方法:
xhr.open("get", "example.txt", false);
xhr.send(null);
这里的 send()方法接收一个参数,即要作为请求主体发送的数据。如果不需要通过请求主体发送 数据,则必须传入 null,因为这个参数对有些浏览器来说是必需的。调用 send()之后,请求就会被分派到服务器。
在收到响应后,响应的数据会自动填充 XHR 对象的属性,相关的属性简介如下。
- responseText:作为响应主体被返回的文本。
- responseXML:如果响应的内容类型是"text/xml"或"application/xml",这个属性中将保 存包含着响应数据的 XML DOM 文档。
- status:响应的 HTTP 状态。
- statusText:HTTP 状态的说明。
XHR 对象的 readyState 属性,该属性表示请求/响应过程的当前活动阶段。这个属性可取的值如下。
- 0:未初始化。尚未调用 open()方法。
- 1:启动。已经调用 open()方法,但尚未调用 send()方法。
- 2:发送。已经调用 send()方法,但尚未接收到响应。
- 3:接收。已经接收到部分响应数据。
- 4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。
只要 readyState 属性的值由一个值变成另一个值,都会触发一次 readystatechange 事件。可 以利用这个事件来检测每次状态变化后 readyState 的值。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open("get", "example.txt", true);
xhr.send(null);
另外,在接收到响应之前还可以调用 abort()方法来取消异步请求,如下所示:
xhr.abort();
21.1.2 HTTP头部信息
每个 HTTP 请求和响应都会带有相应的头部信息,默认情况下,在发送 XHR 请求的同时,还会发送下列头部信息。
- Accept:浏览器能够处理的内容类型。
- Accept-Charset:浏览器能够显示的字符集。
- Accept-Encoding:浏览器能够处理的压缩编码。
- Accept-Language:浏览器当前设置的语言。
- Connection:浏览器与服务器之间连接的类型。
- Cookie:当前页面设置的任何 Cookie。
- Host:发出请求的页面所在的域 。
- Referer:发出请求的页面的 URI。注意,HTTP 规范将这个头部字段拼写错了,而为保证与规 范一致,也只能将错就错了。(这个英文单词的正确拼法应该是 referrer。)
- User-Agent:浏览器的用户代理字符串。
使用 setRequestHeader()方法可以设置自定义的请求头部信息。这个方法接受两个参数:头部字段
的名称和头部字段的值。要成功发送请求头部信息,必须在调用 open()方法之后且调用 send()方法之前调用 setRequestHeader()
// 代码省略
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
调用 XHR 对象的 getResponseHeader()方法并传入头部字段名称,可以取得相应的响应头部信
息。而调用 getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串。
var myHeader = xhr.getResponseHeader("MyHeader");
var allHeaders = xhr.getAllResponseHeaders();
在没有自定义信息的情况下,getAllResponseHeaders()方法通常会返回如下所示的多行文本内容:
Date: Sun, 14 Nov 2004 18:04:03 GMT
Server: Apache/1.3.29 (Unix)
Vary: Accept
X-Powered-By: PHP/4.3.8
Connection: close
Content-Type: text/html; charset=iso-8859-1
21.1.3 GET请求
GET 是最常见的请求类型,最常用于向服务器查询某些信息。必要时,可以将查询字符串参数追加 到 URL 的末尾,以便将信息发送给服务器。对 XHR 而言,位于传入 open()方法的 URL 末尾的查询字符串必须经过正确的编码才行。
- 查询字符串放在URL末尾以?拼接
- 每个参数的名称和值都必须使用 encodeURIComponent()进行编码,然后才能放到 URL 的末尾
- 所有名-值对儿都必须由和号(&)分隔
21.1.4 POST请求
POST 请求的主体可以包含非常多的数据,而且格式不限。
xhr.open("post", "example.php", true);
发送 POST 请求的第二步就是向 send()方法中传入某些数据。
xhr.send(data);
21.2 XMLHttpRequest 2 级
21.2.1 FormData
FormData 为序列化表单以及创建与表单格式相同的数据(用于通过 XHR 传输)提供了便利。
var data = new FormData();
data.append("name", "Nicholas");
这个 append()方法接收两个参数:键和值,分别对应表单字段的名字和字段中包含的值。
另一种通过向 FormData 构造函数中传入表单元素,也可以用表单元素的数据预先向其中填入键值对儿:
var data = new FormData(document.forms[0]);
创建了 FormData 的实例后,可以将它直接传给 XHR 的 send()方法
var xhr = createXHR();
xhr.onreadystatechange = function(){
// 代码省略
}
xhr.open("post","postexample.php", true);
var form = document.getElementById("user-info");
xhr.send(new FormData(form));
使用 FormData 的方便之处体现在不必明确地在 XHR 对象上设置请求头部。XHR 对象能够识别传 入的数据类型是 FormData 的实例,并配置适当的头部信息。
21.2.2 超时设定
在给timeout 设置一个数值后,如果在规定的时间内浏览器还没有接收到响应,那么就会触发 timeout 事件,进而会调用 ontimeout 事件处理程序。
var xhr = createXHR();
xhr.onreadystatechange = function(){
// 代码省略
}
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; //将超时设置为 1 秒钟(仅适用于 IE8+)
xhr.ontimeout = function(){
alert("Request did not return in a second.");
};
xhr.send(null);
21.2.3 overrideMimeType()方法
Firefox 最早引入了 overrideMimeType()方法,用于重写 XHR 响应的 MIME 类型。
var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
这个例子强迫 XHR 对象将响应当作 XML 而非纯文本来处理。调用 overrideMimeType()必须在 send()方法之前,才能保证重写响应的 MIME 类型。
21.3 进度事件
Progress Events 规范定义了与客户端服务器通信有关的事件。
- loadstart:在接收到响应数据的第一个字节时触发。
- progress:在接收响应期间持续不断地触发。
- error:在请求发生错误时触发。
- abort:在因为调用 abort()方法而终止连接时触发。
- load:在接收到完整的响应数据时触发。
- loadend:在通信完成或者触发 error、abort 或 load 事件后触发。
每个请求都从触发 loadstart 事件开始,接下来是一或多个 progress 事件,然后触发 error、 abort 或 load 事件中的一个,最后以触发 loadend 事件结束。
21.3.1 load事件
响应接收完毕后将触发 load 事件,因此也就没有必要去检查 readyState 属性了。而 onload 事件处理程序会接收到一个 event 对象,其 target 属性就指向 XHR 对象实例,因而可以访问到 XHR 对象的所有方法和属性。
var xhr = createXHR();
xhr.onload = function(){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
21.3.2 progress事件
这个事件会在浏览器接收新数据期间周期性地触发。onprogress 事件处理程序会接收到一个 event 对象。其event 对象有如下属性:
- target:指向XHR 对象
- lengthComputable: 表示进度信息是否可用的布尔值
- position: 表示已经接收的字节数
- totalSize: 表示根据Content-Length 响应头部确定的预期字节数。
var xhr = createXHR();
xhr.onload = function(event){
// 代码省略
}
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of " +
event.totalSize +" bytes";
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
必须在调用 open()方法之前添加 onprogress 事件处理程序
21.4 跨源资源共享
默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。
CORS(Cross-Origin Resource Sharing,跨源资源共享)是 W3C 的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
发送该请求时,需要给它附加一个额外的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。
Origin: http://www.nczonline.net
如果服务器认为这个请求可以接受,就在 Access-Control-Allow-Origin 头部中回发相同的源 信息(如果是公共资源,可以回发"*")。
Access-Control-Allow-Origin: http://www.nczonline.net
21.4.1 IE对CORS的实现
微软在 IE8 中引入了 XDR(XDomainRequest)类型。这个对象与 XHR 类似,但能实现安全可靠 的跨域通信。
21.4.2其他浏览器对CORS的实现
使用标准的 XHR 对象并在 open()方法中传入绝对 URL
xhr.open("get", "http://www.somewhere-else.com/page/");
21.4.3 Preflighted Reqeusts
CORS 通过一种叫做 Preflighted Requests 的透明服务器验证机制支持开发人员使用自定义的头部、GET 或 POST 之外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务器发送一个 Preflight 请求。
- Origin:与简单的请求相同。
- Access-Control-Request-Method:请求自身使用的方法。
- Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。
21.4.4 带凭据的请求
默认情况下,跨源请求不提供凭据(cookie、HTTP 认证及客户端 SSL 证明 等 )。通 过 将 withCredentials 属性设置为 true,可以指定某个请求应该发送凭据。如果服务器接受带凭据的请 求,会用下面的 HTTP 头部来响应。
Access-Control-Allow-Credentials: true
21.5 其他跨域技术
21.5.1 图像Ping
动态创建图像经常用于图像 Ping。图像 Ping 是与服务器进行简单、单向的跨域通信的一种方式。 请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或 204 响应。通过图像 Ping,浏览器得不到任何具体的数据,但通过侦听 load 和 error 事件,它能知道响应是什么时候接收到的。
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
21.5.2 JSONP
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
JSONP 是通过动态<script>元素来使用的,使用时可以为src 属性指定一个跨域 URL。这里的<script>元素与<img>元素类似,都有能力不受限制地从其他域加载资源。因为 JSONP 是有效的 JavaScript 代码,所以在请求完成后,即在 JSONP 响应加载到页面中以后,就会立即执行。
function handleResponse(response){
alert("You’re at IP address " + response.ip + ", which is in " response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
JSONP 之所以在开发人员中极为流行,主要原因是它非常简单易用。与图像 Ping 相比,它的优点 在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信。
21.5.3 Comet
Comet 是 Alex Russell①发明的一个词儿,指的是一种更高级的 Ajax 技术,又叫服务器推送。。Ajax 是一种从页面向服务器请求数据的技术,而 Comet 则是一种服务器向页面推送数据的技
术。Comet 能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。
有两种实现 Comet 的方式:长轮询和流。
长轮询是传统轮询(也称为短轮询)的一个翻版,即浏览器定时向服务器发送请求,看有没有更新的数据。两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。
HTTP流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个 HTTP 连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。
21.5.4 SSE服务器发送事件
SSE(Server-Sent Events,服务器发送事件)是围绕只读 Comet 交互推出的 API 或者模式。。SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的 MIME类型必须是 text/event-stream,而且是浏览器中的 JavaScript API 能解析格式输出。
创建一个新的 EventSource 对象
var source = new EventSource("myevents.php");
注意:传入的 URL 必须与创建对象的页面同源
EventSource 的实例有一个 readyState 属性,属性值如下:
- 0 表示正连接到服务器
- 1 表示打开了连接
- 2 表示关闭了连接
另外,还有以下三个事件。
- open:在建立连接时触发。
- message:在从服务器接收到新事件时触发。
- error:在无法建立连接时触发。
source.onmessage = function(event){
var data = event.data;
//处理数据
};
EventSource 对象会保持与服务器的活动连接。如果连接断开,还会重新连接。想要立即断开,必须调用close()方法
source.close();
21.5.5 WebSockets
Web Sockets 的目标是在一个单独的持久连接上提供全双工、双向通信。只有支持WebSocket 协议的服务器才能正常工作。
WebSockets 使用了自定义的协议,未加密的连接ws:// ,加密的连接wss:// 。
要创建 Web Socket,先实例一个 WebSocket 对象并传入要连接的 URL
var socket = new WebSocket("ws://www.example.com/server.php");
注意,必须给 WebSocket 构造函数传入绝对 URL。同源策略对 WebSockets 不适用,因此可以通
过它打开到任何站点的连接。至于是否会与某个域中的页面通信,则完全取决于服务器。
WebSocket 也有一个表示当前状态的 readyState 属性:
- WebSocket.OPENING (0):正在建立连接。
- WebSocket.OPEN (1):已经建立连接。
- WebSocket.CLOSING (2):正在关闭连接。
- WebSocket.CLOSE (3):已经关闭连接。
要向服务器发送数据,使用 send()方法并传入任意字符串,复杂的数据结构必须序列化。
var socket = new WebSocket("ws://www.example.com/server.php");
socket.send("Hello world!");
当服务器向客户端发来消息时,WebSocket 对象就会触发 message 事件
socket.onmessage = function(event){
var data = event.data;
//处理数据
};
要关闭 Web Socket 连接,可以在任何时候调用 close()方法。
socket.close();
WebSocket 对象还有其他三个事件,在连接生命周期的不同阶段触发。
- open:在成功建立连接时触发。
- error:在发生错误时触发,连接不能持续。
- close:在连接关闭时触发。
其中close事件对象有三个额外的属性:
- wasClean 是一个布尔值,表示连接是否已经明确地关闭;
- code 是服务器返回的数值状态码;
- reason 是一个字符串,包含服务器发回的消息
socket.onclose = function(event){
console.log("Was clean? " + event.wasClean + " Code=" + event.code + " Reason="+ event.reason);
};