ajax技术能够向服务器请求额外的数据而无须卸载页面,会带来更好的用户体验。
XMLHttpRequest 对象
Ajax
技术的核心是
XMLHttpRequest
对象(简称
XHR
)
创建
XHR
对象
var xhr = new XMLHttpRequest();
XHR的用法
要调用的第一个方法是
open()
,它接受
3
个参数:要发送的请求的类型("get"
、
"post"
等)、请求的
URL
和表示是否异步发送请求的布尔值。
xhr.open("get", "example.php", false);
xhr.send(null);
这里的
send()
方法接收一个参数,即要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入 null
,因为这个参数对有些浏览器来说是必需的。调用
send()
之后,请求就会被分派到服务器。
在收到响应后,响应的数据会自动填充 XHR
对象的属性,相关的属性简介如下。
- responseText:作为响应主体被返回的文本。
- responseXML:如果响应的内容类型是"text/xml"或"application/xml",这个属性中将保存包含着响应数据的 XML DOM 文档。
- status:响应的 HTTP 状态。
- statusText:HTTP 状态的说明。
xhr.open("get", "example.txt", false);
xhr.send(null);
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
像前面这样发送同步请求当然没有问题,但多数情况下,我们还是要发送异步请求,才能让 JavaScript 继续执行而不必等待响应。此时,可以检测
XHR
对象的
readyState
属性,该属性表示请求/响应过程的当前活动阶段。这个属性可取的值如下。
- 0:未初始化。尚未调用 open()方法。
- 1:启动。已经调用 open()方法,但尚未调用 send()方法。
- 2:发送。已经调用 send()方法,但尚未接收到响应。
- 3:接收。已经接收到部分响应数据。
- 4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。
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);
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined"){
if (typeof arguments.callee.activeXString != "string"){
var versions = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"],
i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}
HTTP头部信息
每个
HTTP
请求和响应都会带有相应的头部信息
- Accept:浏览器能够处理的内容类型。
- Accept-Charset:浏览器能够显示的字符集。
- Accept-Encoding:浏览器能够处理的压缩编码。
- Accept-Language:浏览器当前设置的语言。
- Connection:浏览器与服务器之间连接的类型。
- Cookie:当前页面设置的任何 Cookie。
- Host:发出请求的页面所在的域 。
-
Referer :发出请求的页面的 URI 。
-
User-Agent :浏览器的用户代理字符串。
使用
setRequestHeader()
方法可以设置自定义的请求头部信息。
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.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
调用
XHR
对象的
getResponseHeader()
方法并传入头部字段名称,可以取得相应的响应头部信息。
而调用
getAllResponseHeaders()
方法则可以取得一个包含所有头部信息的长字符串。
var myHeader = xhr.getResponseHeader("MyHeader");
var allHeaders = xhr.getAllResponseHeaders();
GET请求
xhr.open("get", "example.php?name1=value1&name2=value2", true);
下面这个函数可以辅助向现有 URL 的末尾添加查询字符串参数:这个 addURLParam()函数接受三个参数:要添加参数的 URL、参数的名称和参数的值。
function addURLParam(url, name, value) {
url += (url.indexOf("?") == -1 ? "?" : "&");
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
下面是使用这个函数来构建请求 URL 的示例。
var url = "example.php";
//添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化请求
xhr.open("get", url, false);
POST请求
xhr.open("post", "example.php", true);
function submitData(){
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("post", "postexample.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var form = document.getElementById("user-info");
xhr.send(serialize(form));
}
XMLHttpRequest 2 级
FormData
FormData 为序列化表单以及创建与表单格式相同的数据(用于通过 XHR 传输)提供了便利。
下面的代码创建了一个 FormData 对象,并向其中添加了一些数据。
var data = new FormData();
data.append("name", "Nicholas");
这个 append()方法接收两个参数:键和值,分别对应表单字段的名字和字段中包含的值。
超时设定
IE8
为
XHR
对象添加了一个
timeout
属性,表示请求在等待响应多少毫秒之后就终止。在给 timeout 设置一个数值后,如果在规定的时间内浏览器还没有接收到响应,那么就会触发
timeout
事件,进而会调用 ontimeout
事件处理程序。
var xhr = createXHR();
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
try {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
} catch (ex){
//假设由 ontimeout 事件处理程序处理
}
}
};
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);
overrideMimeType()方法
overrideMimeType()
方法,用于重写
XHR
响应的
MIME
类型。
比如,服务器返回的
MIME
类型是
text/plain
,但数据中实际包含的是
XML
。根据
MIME
类型,即使数据是 XML
,
responseXML
属性中仍然是
null
。通过调用
overrideMimeType()
方法,可以保证把响应当作 XML
而非纯文本来处理。
var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
进度事件
- loadstart:在接收到响应数据的第一个字节时触发。
- progress:在接收响应期间持续不断地触发。
- error:在请求发生错误时触发。
- abort:在因为调用 abort()方法而终止连接时触发。
- load:在接收到完整的响应数据时触发。
- loadend:在通信完成或者触发 error、abort 或 load 事件后触发。
load事件
load
事件,用以替代
readystatechange
事件。
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);
progress事件
这个事件会在浏览器接收新数据期间周期性地触发。
var xhr = createXHR();
xhr.onload = function(event){
if ((xhr.status >= 200 && xhr.status < 300) ||
xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
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);
跨源资源共享
CORS
(
Cross-Origin Resource Sharing
,跨源资源共享)是
W3C
的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS
背后的基本思想,就是使用自定义的
HTTP
头部浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
Origin: http://www.nczonline.net
比如一个简单的使用
GET
或
POST
发送的请求,它没有自定义的头部,而主体内容是
text/plain
。在发送该请求时,需要给它附加一个额外的 Origin
头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。
如果服务器认为这个请求可以接受,就在
Access-Control-Allow-Origin
头部中回发相同的源
信息(如果是公共资源,可以回发
"*"
)。例如:
Access-Control-Allow-Origin: http://www.nczonline.net
IE对CORS的实现
微软在
IE8
中引入了
XDR
(
XDomainRequest
)类型。这个对象与
XHR
类似,但能实现安全可靠
的跨域通信。
XDR
对象的安全机制部分实现了
W3C
的
CORS
规范。
以下是
XDR
与
XHR
的一些不同之处。
- cookie 不会随请求发送,也不会随响应返回。
- 只能设置请求头部信息中的 Content-Type 字段。
- 不能访问响应头部信息。
- 只支持 GET 和 POST 请求。
XDR
对象的使用方法与
XHR
对象非常相似。也是创建一个
XDomainRequest
的实例,调
open()
方法,再调用
send()
方法。但与
XHR
对象的
open()
方法不同,
XDR
对象的
open()
方法只接收两个参数:请求的类型和 URL
。
var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);
在请求返回前调用
abort()
方法可以终止请求:
xdr.abort(); //终止请求
与
XHR
一样,
XDR
对象也支持
timeout
属性以及
ontimeout
事件处理程序。
var xdr = new XDomainRequest();
xdr.onload = function(){
alert(xdr.responseText);
};
xdr.onerror = function(){
alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);
其他浏览器对CORS的实现
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", "http://www.somewhere-else.com/page/", true);
xhr.send(null);
Preflighted Reqeusts
这种请求使用 OPTIONS 方法,发送下列头部
- Origin:与简单的请求相同。
- Access-Control-Request-Method:请求自身使用的方法。
- Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。以下是一个带有自定义头部 NCZ 的使用 POST 方法发送的请求。
例如:
- Origin: http://www.nczonline.net
- Access-Control-Request-Method: POST
- Access-Control-Request-Headers: NCZ
发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与
浏览器进行沟通。
- Access-Control-Allow-Origin:与简单的请求相同。
- Access-Control-Allow-Methods:允许的方法,多个方法以逗号分隔。
- Access-Control-Allow-Headers:允许的头部,多个头部以逗号分隔。
- Access-Control-Max-Age:应该将这个 Preflight 请求缓存多长时间(以秒表示)。
例如:
- Access-Control-Allow-Origin: http://www.nczonline.net
- Access-Control-Allow-Methods: POST, GET
- Access-Control-Allow-Headers: NCZ
- Access-Control-Max-Age: 1728000
带凭据的请求
默认情况下,跨源请求不提供凭据(
cookie
、
HTTP
认证及客户端
SSL
证明 等 )。 通 过 将
withCredentials
属性设置为
true
,可以指定某个请求应该发送凭据。如果服务器接受带凭据的请
求,会用下面的
HTTP
头部来响应。
Access-Control-Allow-Credentials: true
跨浏览器的CORS
即使浏览器对
CORS
的支持程度并不都一样,但所有浏览器都支持简单的(非
Preflight
和不带凭据的)请求,因此有必要实现一个跨浏览器的方案。检测 XHR
是否支持
CORS
的最简单方式,就是检查是否存在 withCredentials
属性。再结合检测
XDomainRequest
对象是否存在,就可以兼顾所有浏览器了。
function createCORSRequest(method, url){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr){
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined"){
vxhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var request = createCORSRequest("get", "http://www.somewhere-else.com/page/");
if (request){
request.onload = function(){
//对 request.responseText 进行处理
};
request.send();
}
- abort():用于停止正在进行的请求。
- onerror:用于替代 onreadystatechange 检测错误。
- onload:用于替代 onreadystatechange 检测成功。
- responseText:用于取得响应内容。
- send():用于发送请求。
以上成员都包含在
createCORSRequest()
函数返回的对象中,在所有浏览器中都能正常使用。
其他跨域技术
图像Ping
上述第一种跨域请求技术是使用
<img>标签。动态创建图像经常用于
图像
Ping
。图像
Ping
是与服务器进行简单、单向的跨域通信的一种方式。
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
图像
Ping
最常用于跟踪用户点击页面或动态广告曝光次数。图像
Ping
有两个主要的缺点,一是只
能发送 GET
请求,二是无法访问服务器的响应文本。因此,图像
Ping
只能用于浏览器与服务器间的单向通信。
JSONP
JSONP
由两部分组成:回调函数和数据。
回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON
数据。
http://freegeoip.net/json/?callback=handleResponse
JSONP
是通过动态
<script>
元素
来使用的,使用时可以为src 属性指定一个跨域
URL
。
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);
Comet
Comet
是
Alex Russell
①
发明的一个词儿,指的是一种更高级的
Ajax
技术(经常也有人称为“服务器推送”)。Ajax
是一种从页面向服务器请求数据的技术,而
Comet
则是一种服务器向页面推送数据的技术。Comet
能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。
有两种实现
Comet
的方式:
长轮询和流
。
轮询
无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两者最大的区别
在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。轮询的优势是所有浏览器都支持,因为使用 XHR
对象和
setTimeout()
就能实现。
HTTP 流
它在页面的整个生命周期内只使用一个 HTTP
连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。
使 用 XHR
对象实现
HTTP
流的典型代码如下所示。
function createStreamingClient(url, progress, finished){
var xhr = new XMLHttpRequest(),
received = 0;
xhr.open("get", url, true);
xhr.onreadystatechange = function(){
var result;
if (xhr.readyState == 3){
//只取得最新数据并调整计数器
result = xhr.responseText.substring(received);
received += result.length;
//调用 progress 回调函数
progress(result);
} else if (xhr.readyState == 4){
finished(xhr.responseText);
}
};
xhr.send(null);
return xhr;
}
var client = createStreamingClient("streaming.php", function(data){
alert("Received: " + data);
}, function(data){
alert("Done!");
});
这个
createStreamingClient()
函数接收三个参数:要连接的
URL
、在接收到数据时调用的函
数以及关闭连接时调用的函数。
Web Sockets
Web Sockets
的目标是在一个单独的持久连接上提供全双工、双向通信。
1. Web Sockets API
要创建
Web Socket
,先实例一个
WebSocket
对象并传入要连接的
URL
:
var socket = new WebSocket("ws://www.example.com/server.php");
实例化了
WebSocket
对象后,浏览器就会马上尝试创建连接。与
XHR
类似,
WebSocket
也有一
个表示当前状态的
readyState
属性。不过,这个属性的值与
XHR
并不相同,而是如下所示。
- WebSocket.OPENING (0):正在建立连接。
- WebSocket.OPEN (1):已经建立连接。
- WebSocket.CLOSING (2):正在关闭连接。
- WebSocket.CLOSE (3):已经关闭连接。
2. 发送和接收数据
Web Socket
打开之后,就可以通过连接发送和接收数据。要向服务器发送数据,使用
send()
方法
并传入任意字符串。
var socket = new WebSocket("ws://www.example.com/server.php");
socket.send("Hello world!");
因为
Web Sockets
只能通过连接发送纯文本数据,所以对于复杂的数据结构,在通过连接发送之前,必须进行序列化。下面的例子展示了先将数据序列化为一个 JSON
字符串,然后再发送到服务器:
var message = {
time: new Date(),
text: "Hello world!",
clientId: "asdfp8734rew"
};
socket.send(JSON.stringify(message));
当服务器向客户端发来消息时,
WebSocket
对象就会触发
message
事件。这个
message
事件与
其他传递消息的协议类似,也是把返回的数据保存在
event.data
属性中。
socket.onmessage = function(event){
var data = event.data;
//处理数据
};
3. 其他事件
WebSocket
对象还有其他三个事件,在连接生命周期的不同阶段触发。
- open:在成功建立连接时触发。
- error:在发生错误时触发,连接不能持续。
- close:在连接关闭时触发。
var socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function(){
alert("Connection established.");
};
socket.onerror = function(){
alert("Connection error.");
};
socket.onclose = function(){
alert("Connection closed.");
};