目前,主要有5种常用技术用于向服务器请求数据:
❑XMLHttpRequest(XHR)
❑Dynamic script tag insertion(动态脚本标签插入)
❑iframe
❑Comet
❑Multipart XHR(多部分XHR)
在现代高性能JavaScript中,推荐使用的技术是XHR、动态脚本标签插入和多部分XHR。作为数据传输技术,往往在极限情况下使用Comet和iframe。
(1)XHR
目前最常用的方法是使用XHR实现异步收发数据。目前所有浏览器都能够很好地支持这种方法,而且能够精细地控制发送请求和接收数据。可以向请求中添加任意的头信息和参数(包括GET和POST),读取从服务器返回的头信息,以及响应文本自身。
var url=/'/data.php/';
var params=[/'id=934875/',/'limit=20/'];
var req=new XMLHttpRequest;
req.onreadystatechange=function{
if(req.readyState===4){
var responseHeaders=req.getAllResponseHeaders;
var data=req.responseText;
}
}
req.open(/'GET/',url+/'?/'+params.join(/'&/'),true);
req.setRequestHeader(/'X-Requested-With/',/'XMLHttpRequest/');
req.send(null);
上面代码显示了如何从URL请求数据、使用参数,以及如何读取响应报文和头信息。readyState等于4表示整个响应报文已经接收完并可用于操作。readyState等于3则表示此时正在与服务器交互,响应报文还在传输之中。这就是所谓的流,它是提高数据请求性能的强大工具。
req.onreadystatechange=function{
if(req.readyState===3){//一些但不是全部数据被接收
var dataSoFar=req.responseText;
}else if(req.readyState===4){//所有数据被接收
var data=req.responseText;
}
}
由于XHR提供了高级别的控制,浏览器增加了一些限制,因此用户不能使用XHR从当前运行的代码域之外请求数据,更何况早期版本的IE也不提供readyState=3,不支持流。像对待一个字符串或者一个XML对象那样对待从请求返回的数据,这意味着处理大量数据将相当缓慢。
尽管有这些缺点,XHR仍然是最常用的请求数据技术,也是最强大的,因此它应当成为异步数据通信的首选。当使用XHR请求数据时,可以选择POST或GET。如果请求不改变服务器状态只是取回数据,则使用GET。GET请求被缓冲起来,多次提取相同的数据可提高性能。
只有当URL和参数的长度超过了2048个字符时才使用POST提取数据,这是因为IE限制URL的长度,过长将导致请求(参数)被截断。
(2)动态脚本标签插入
该技术克服了XHR的最大限制,可以从不同域的服务器上获取数据。这是一种黑客技术,而不是实例化一个专用对象,利用JavaScript创建了一个新脚本标签,并将它的源属性设置为一个指向不同域的URL。
var scriptElement=document.createElement(/'script/');
scriptElement.src=/'http://any-domain.com/Javascript/lib.js/';
document.getElementsByTagName_r(/'head/')[0].appendChild(scriptElement);
动态脚本标签插入与XHR相比只提供更少的控制。用户不能通过请求发送信息头。参数只能通过GET方法传递,不能用POST。不能设置请求的超时或重试,并且必须等待所有数据返回之后才可以访问它们。也不能访问响应信息头或像访问字符串那样访问整个响应报文。
因为响应报文被用做脚本标签的源码,所以它必须是可执行的JavaScript。任何数据,无论什么格式,必须在一个回调函数之中被组装起来。
var scriptElement=document.createElement(/'script/');
scriptElement.src=/'http://any-domain.com/Javascript/lib.js/';
document.getElementsByTagName_r(/'head/')[0].appendChild(scriptElement);
function jsonCallback(jsonString){
var data=(/'(/'+jsonString+/')/');
}
在上面示例中,lib.js文件将调用jsonCallback函数组装数据:
jsonCallback({/"status/":1,/"colors/":[/"#fff/",/"#000/",/"#ff0000/"]});
尽管有这些限制,此技术在数据传输上仍然是非常迅速的。其响应结果是运行JavaScript,而不是必须作为字符串被进一步处理。正因为如此,它是客户端获取并解析数据最快的方法。
由于JavaScript中没有权限或访问控制的概念,所以页面上任何使用动态脚本标签插入的代码都可以完全控制整个页面,包括修改任何内容、将用户重定向到另一个站点,以及跟踪在页面上的操作并将数据发送给第三方。使用外部来源的代码时要非常小心。
(3)多部分XHR
多部分XHR允许只用一个HTTP请求就可以从服务器端获取多个资源。它将资源(可以是CSS文件、HTML片段、JavaScript代码,或base64编码的图片)打包成一个由特定分隔符界定的长字符串,将其从服务器端发送到客户端。JavaScript代码处理此长字符串,根据它的媒体类型和其他“信息头”解析出每个资源。首先,发送一个请求向服务器索取几个图像资源:
var req=new XMLHttpRequest;
req.open(/'GET/',/'rollup_images.php/',true);
req.onreadystatechange=function{
if(req.readyState==4){
splitImages(req.responseText);
}
};
req.send(null);
这是一个非常简单的请求:向rollup_images.php请求数据,一旦收到返回结果,就将它交给函数splitImages处理。
然后,服务器读取图片并将它们转换为字符串:
$images=array(/'kitten.jpg/',/'sunset.jpg/',/'baby.jpg/');
foreach($images as$image){
$image_fh=fopen($image,/'r/');
$image_data=fread($image_fh,filesize($image));
fclose($image_fh);
$payloads=base64_encode($image_data);
}
$newline=chr(1);
echo implode($newline,$payloads);
这段PHP代码读取3张图片,并将它们转换成base64字符串。这些字符串之间用一个简单的字符连接起来,然后返回给客户端。
接下来回到客户端,此数据由splitImages函数处理:
function splitImages(imageString){
var imageData=imageString.split(/"u0001/");
var imageElement;
for(var i=0,len=imageData.length;i<len;i++){
imageElement=document.createElement(/'img/');
imageElement.src=/'data:image/jpeg;base64,/'+imageData[i];
document.getElementById(/'container/').appendChild(imageElement);
}
}
此函数将拼接而成的字符串分解为3段,每段用于创建一个图像元素,然后将图像元素插入页面中。图像不是从base64转换成二进制,而是使用data:URL并指定image/jpeg媒体类型。
最终结果:在一次HTTP请求中向浏览器传入了3张图片。也可以传入20张或100张图片,这样响应报文会更大,但也只是一次HTTP请求。这种响应也可以扩展至其他类型的资源,JavaScript文件、CSS文件、HTML片段、不同类型的图片都可以合并成一次响应。任何数据类型都可作为一个JavaScript处理的字符串被发送。下面的函数用于将JavaScript代码、CSS样式表和图片转换为浏览器可用的资源。
function handleImageData(data,mimeType){
var img=document.createElement(/'img/');
img.src=/'data:/'+mimeType+/';base64,/'+data;
return img;
}
function handleCss(data){
var style=document.createElement(/'style/');
style.type=/'text/css/';
var node=document.createTextNode(data);
style.appendChild(node);
document.getElementsByTagName_r(/'head/')[0].appendChild(style);
}
function handleJavascript(data){(data);}
由于MXHR响应报文越来越大,有必要在每个资源收到响应时立刻处理,而不是等待整个响应报文接收完成后再处理。这可以通过监听readyState=3实现。
var req=new XMLHttpRequest;
var getLatestPacketInterval,lastLength=0;
req.open(/'GET/',/'rollup_images.php/',true);
req.onreadystatechange=readyStateHandler;
req.send(null);
function readyStateHandler{
if(req.readyState===3&&getLatestPacketInterval===null){
getLatestPacketInterval=window.setInterval(function{
getLatestPacket;
},15);
}
if(req.readyState===4){
clearInterval(getLatestPacketInterval);
getLatestPacket;
}
}
function getLatestPacket{
var length=req.responseText.length;
var packet=req.responseText.substring(lastLength,length);
processPacket(packet);
lastLength=length;
}
当readyState=3第一次发出时,启动一个定时器,每隔15 ms检查一次响应报文中的新数据,数据片段被收集起来直到发现一个分隔符,然后一切都作为一个完整的资源处理。
以健壮的方式使用MXHR的代码很复杂,但值得进一步研究。完整的库可参见http://techfoolery.com/mxhr/。
使用这种技术有一些缺点,其中最大的缺点是以此方法获得的资源不能被浏览器缓存。使用MXHR获取一个特定的CSS文件,然后在下一个页面中正常加载它,会发现它不在缓存中,这是因为整批资源作为一个长字符串传输,然后由JavaScript代码分割。由于没有办法通过程序将文件放入浏览器缓存中,因此用这种方法获取的资源也无法存放在浏览器缓存中。另外,早期版本的IE不支持readyState=3或data:URL,从IE 8开始支持这些技术,在IE 6和IE 7中必须设法变通。在某些情况下,MXHR能够显著地提高整体页面的性能:
❑网页包含许多其他地方不会用到的资源,不需要缓存资源,尤其是图片。
❑网站为每个页面使用了独一无二的打包的JavaScript或CSS文件以减少HTTP请求,因为它们对每个页面来说是独特的,所以不需要从缓存中读取,除非重新载入特定页面。
❑由于HTTP请求是Ajax中最极端的瓶颈之一,减少其需求数量对整个页面性能有很大影响,尤其是将100个图片请求转化为一个MXHR请求时能够极大地提高响应速度。