`

21.1 XMLHttpRequest 对象【JavaScript高级程序设计第三版】

阅读更多

IE5 是第一款引入XHR 对象的浏览器。在IE5 中,XHR 对象是通过MSXML 库中的一个ActiveX对象实现的。因此,在IE 中可能会遇到三种不同版本的XHR 对象,即MSXML2.XMLHttp、MSXML2.XMLHttp.3.0 和MXSML2.XMLHttp.6.0。要使用MSXML 库中的XHR 对象,需要像第18章讨论创建XML 文档时一样,编写一个函数,例如:

//适用于IE7 之前的版本
function createXHR() {
	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);
}

这个函数会尽力根据IE 中可用的MSXML 库的情况创建最新版本的XHR 对象。
IE7+、Firefox、Opera、Chrome 和Safari 都支持原生的XHR 对象,在这些浏览器中创建XHR 对象要像下面这样使用XMLHttpRequest 构造函数。

var xhr = new XMLHttpRequest();

假如你只想支持IE7 及更高版本,那么大可丢掉前面定义的那个函数,而只用原生的XHR 实现。但是,如果你必须还要支持IE 的早期版本,那么则可以在这个createXHR()函数中加入对原生XHR对象的支持。

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.");
	}
}

运行一下
这个函数中新增的代码首先检测原生XHR 对象是否存在,如果存在则返回它的新实例。如果原生对象不存在,则检测ActiveX 对象。如果这两种对象都不存在,就抛出一个错误。然后,就可以使用下面的代码在所有浏览器中创建XHR 对象了。

var xhr = createXHR();

由于其他浏览器中对XHR 的实现与IE 最早的实现是兼容的,因此就可以在所有浏览器中都以相同方式使用上面创建的xhr 对象。

21.1.1 XHR的用法

在使用XHR 对象时,要调用的第一个方法是open(),它接受3 个参数:要发送的请求的类型("get"、"post"等)、请求的URL 和表示是否异步发送请求的布尔值。下面就是调用这个方法的例子。

xhr.open("get", "example.php", false);

这行代码会启动一个针对example.php 的GET 请求。有关这行代码,需要说明两点:一是URL相对于执行代码的当前页面(当然也可以使用绝对路径);二是调用open()方法并不会真正发送请求,而只是启动一个请求以备发送。

只能向同一个域中使用相同端口和协议的URL 发送请求。如果URL 与启动请求的页面有任何差别,都会引发安全错误。

要发送特定的请求,必须像下面这样调用send()方法:

xhr.open("get", "example.txt", false);
xhr.send(null);

运行一下
这里的send()方法接收一个参数,即要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null,因为这个参数对有些浏览器来说是必需的。调用send()之后,请求就会被分派到服务器。
由于这次请求是同步的,JavaScript 代码会等到服务器响应之后再继续执行。在收到响应后,响应的数据会自动填充XHR 对象的属性,相关的属性简介如下。

  • responseText:作为响应主体被返回的文本。
  • responseXML:如果响应的内容类型是"text/xml"或"application/xml",这个属性中将保存包含着响应数据的XML DOM 文档。
  • status:响应的HTTP 状态。
  • statusText:HTTP 状态的说明。

在接收到响应后,第一步是检查status 属性,以确定响应已经成功返回。一般来说,可以将HTTP状态代码为200 作为成功的标志。此时,responseText 属性的内容已经就绪,而且在内容类型正确的情况下,responseXML 也应该能够访问了。此外,状态代码为304 表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本;当然,也意味着响应是有效的。为确保接收到适当的响应,应该像下面这样检查上述这两种状态代码:

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);
}

运行一下
根据返回的状态代码,这个例子可能会显示由服务器返回的内容,也可能会显示一条错误消息。我们建议读者要通过检测status 来决定下一步的操作,不要依赖statusText,因为后者在跨浏览器使用时不太可靠。另外,无论内容类型是什么,响应主体的内容都会保存到responseText 属性中;而对于非XML 数据而言,responseXML 属性的值将为null。

有的浏览器会错误地报告204 状态代码。IE 中XHR 的ActiveX 版本会将204 设置为1223,而IE 中原生的XHR 则会将204 规范化为200。Opera 会在取得204 时报告status 的值为0。

像前面这样发送同步请求当然没有问题,但多数情况下,我们还是要发送异步请求,才能让JavaScript 继续执行而不必等待响应。此时,可以检测XHR 对象的readyState 属性,该属性表示请求/响应过程的当前活动阶段。这个属性可取的值如下。

  • 0:未初始化。尚未调用open()方法。
  • 1:启动。已经调用open()方法,但尚未调用send()方法。
  • 2:发送。已经调用send()方法,但尚未接收到响应。
  • 3:接收。已经接收到部分响应数据。
  • 4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。

只要readyState 属性的值由一个值变成另一个值,都会触发一次readystatechange 事件。可以利用这个事件来检测每次状态变化后readyState 的值。通常,我们只对readyState 值为4 的阶段感兴趣,因为这时所有数据都已经就绪。不过,必须在调用open()之前指定onreadystatechange事件处理程序才能确保跨浏览器兼容性。下面来看一个例子。

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);

运行一下
以上代码利用DOM 0 级方法为XHR 对象添加了事件处理程序,原因是并非所有浏览器都支持DOM 2级方法。与其他事件处理程序不同,这里没有向onreadystatechange 事件处理程序中传递event 对象;必须通过XHR 对象本身来确定下一步该怎么做。

这个例子在onreadystatechange 事件处理程序中使用了xhr 对象,没有使用this 对象,原因是onreadystatechange 事件处理程序的作用域问题。如果使用this 对象,在有的浏览器中会导致函数执行失败,或者导致错误发生。因此,使用实际的XHR 对象实例变量是较为可靠的一种方式。

另外,在接收到响应之前还可以调用abort()方法来取消异步请求,如下所示:

xhr.abort();

调用这个方法后,XHR 对象会停止触发事件,而且也不再允许访问任何与响应有关的对象属性。在终止请求之后,还应该对XHR 对象进行解引用操作。由于内存原因,不建议重用XHR 对象。

21.1.2 HTTP头部信息

每个HTTP 请求和响应都会带有相应的头部信息,其中有的对开发人员有用,有的也没有什么用。XHR 对象也提供了操作这两种头部(即请求头部和响应头部)信息的方法。默认情况下,在发送XHR 请求的同时,还会发送下列头部信息。

  • Accept:浏览器能够处理的内容类型。
  • Accept-Charset:浏览器能够显示的字符集。
  • Accept-Encoding:浏览器能够处理的压缩编码。
  • Accept-Language:浏览器当前设置的语言。
  • Connection:浏览器与服务器之间连接的类型。
  • Cookie:当前页面设置的任何Cookie。
  • Host:发出请求的页面所在的域 。
  • Referer:发出请求的页面的URI。注意,HTTP 规范将这个头部字段拼写错了,而为保证与规范一致,也只能将错就错了。(这个英文单词的正确拼法应该是referrer。)
  • User-Agent:浏览器的用户代理字符串。

虽然不同浏览器实际发送的头部信息会有所不同,但以上列出的基本上是所有浏览器都会发送的。使用setRequestHeader()方法可以设置自定义的请求头部信息。这个方法接受两个参数:头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在调用open()方法之后且调用send()方法之前调用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();

在服务器端,也可以利用头部信息向浏览器发送额外的、结构化的数据。在没有自定义信息的情况下,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 末尾的查询字符串必须经过正确的编码才行。
使用GET 请求经常会发生的一个错误,就是查询字符串的格式有问题。查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,然后才能放到URL 的末尾;而且所有名-值对儿都必须由和号(&)分隔,如下面的例子所示。

xhr.open("get", "example.php?name1=value1&name2=value2", true);

下面这个函数可以辅助向现有URL 的末尾添加查询字符串参数:

function addURLParam(url, name, value) {
	url += (url.indexOf("?") == -1 ? "?": "&");
	url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
	return url;
}

这个addURLParam()函数接受三个参数:要添加参数的URL、参数的名称和参数的值。这个函数首先检查URL 是否包含问号(以确定是否已经有参数存在)。如果没有,就添加一个问号;否则,就添加一个和号。然后,将参数名称和值进行编码,再添加到URL 的末尾。最后返回添加参数之后的URL。
下面是使用这个函数来构建请求URL 的示例。

var url = "example.php";
//添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
//初始化请求
xhr.open("get", url, false);

在这里使用addURLParam()函数可以确保查询字符串的格式良好,并可靠地用于XHR 对象。

21.1.4 POST请求

使用频率仅次于GET 的是POST 请求,通常用于向服务器发送应该被保存的数据。POST 请求应该把数据作为请求的主体提交,而GET 请求传统上不是这样。POST 请求的主体可以包含非常多的数据,而且格式不限。在open()方法第一个参数的位置传入"post",就可以初始化一个POST 请求,如下面的例子所示。

xhr.open("post", "example.php", true);

发送POST 请求的第二步就是向send()方法中传入某些数据。由于XHR 最初的设计主要是为了处理XML,因此可以在此传入XML DOM 文档,传入的文档经序列化之后将作为请求主体被提交到服务器。当然,也可以在此传入任何想发送到服务器的字符串。
默认情况下,服务器对POST 请求和提交Web 表单的请求并不会一视同仁。因此,服务器端必须有程序来读取发送过来的原始数据,并从中解析出有用的部分。不过,我们可以使用XHR 来模仿表单提交:首先将Content-Type 头部信息设置为application/x-www-form-urlencoded,也就是表单提交时的内容类型,其次是以适当的格式创建一个字符串。第14 章曾经讨论过,POST 数据的格式与查询字符串格式相同。如果需要将页面中表单的数据进行序列化,然后再通过XHR 发送到服务器,那么就可以使用第14 章介绍的serialize()函数来创建这个字符串:

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));
}

运行一下
这个函数可以将ID 为"user-info"的表单中的数据序列化之后发送给服务器。而下面的示例PHP文件postexample.php 就可以通过$_POST 取得提交的数据了:

<?php
header("Content-Type: text/plain");
echo <<<EOF
Name: {$_POST[‘user-name’]}
Email: {$_POST[‘user-email’]}
EOF;
?>

如果不设置Content-Type 头部信息,那么发送给服务器的数据就不会出现在$_POST 超级全局变量中。这时候,要访问同样的数据,就必须借助$HTTP_RAW_POST_DATA。

与GET 请求相比,POST 请求消耗的资源会更多一些。从性能角度来看,以发送相同的数据计,GET 请求的速度最多可达到POST 请求的两倍。
写道

 

1
1
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    ASP.NET4高级程序设计第4版 带目录PDF 分卷压缩包 part1

    ASP.NET 4高级程序设计(第4版)》【原版书为:Pro ASP.NET 4 in C# 2010】是ASP.NET领域的鸿篇巨制,全面讲解了ASP.NET4的各种特性及其背后的工作原理,并给出了许多针对如何构建复杂、可扩展的网站从实践中得出的...

    ASP.NET4高级程序设计(第4版) 3/3

     《ASP.NET 4高级程序设计(第4版)》是ASP.NET领域的鸿篇巨制,全面讲解了ASP.NET4的各种特性及其背后的工作原理,并给出了许多针对如何构建复杂、可扩展的网站从实践中得出的建议。书中还深入讲述了其他ASP.NET图书...

    PHP和MySQL Web开发第4版pdf以及源码

    第三篇 电子商务与安全性 第14章 运营一个电子商务网站 14.1 我们要实现什么目标 14.2 考虑电子商务网站的类型 14.2.1 使用在线说明书公布信息 14.2.2 接收产品或服务的订单 14.2.3 提供服务和数字产品 14.2.4...

    PHP和MySQL WEB开发(第4版)

    第三篇 电子商务与安全性 第14章 运营一个电子商务网站 14.1 我们要实现什么目标 14.2 考虑电子商务网站的类型 14.2.1 使用在线说明书公布信息 14.2.2 接收产品或服务的订单 14.2.3 提供服务和数字产品 14.2.4 为...

    PHP和MySQL Web开发第4版

    第三篇 电子商务与安全性 第14章 运营一个电子商务网站 14.1 我们要实现什么目标 14.2 考虑电子商务网站的类型 14.2.1 使用在线说明书公布信息 14.2.2 接收产品或服务的订单 14.2.3 提供服务和数字产品 14.2.4...

Global site tag (gtag.js) - Google Analytics