Ajax,在它最基本的层面,是一种与服务器通信而不重载当前页面的方法,数据可从服务器得到或发送给服务器。有多种不一样的方法构造这种通信通道,每种方法都有本身的优点和限制。
有五种经常使用技术用于向服务器请求数据:
(1)XMLHttpRequest (XHR)
(2)动态脚本标签插入
(3)框架
(4)Comet
(5)多部分的XHR
在现代高性能JavaScript中使用的三种技术是XHR,动态脚本标签插入和多部分的XHR。使用Comet和iframe(做为数据传输技术)每每是极限状况,不在这里讨论。javascript
1、XMLHttpRequest
目前最经常使用的方法中,XMLHttpRequest(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 也不提供readyState3,它不支持流。从请求返回的数据像一个字符串或者一个XML对象那样对待,这意味着处理大量数据将至关缓慢。
尽管有这些缺点,XHR仍旧是最经常使用的请求数据技术,也是最强大的,它应当成为你的首选。
当使用XHR请求数据时,你能够选择POST 或GET。若是请求不改变服务器状态只是取回数据(又称做幂等动做)则使用GET。GET请求被缓冲起来,若是你屡次提取相同的数据可提升性能。
只有当URL和参数的长度超过了2'048个字符时才使用POST提取数据。由于Internet Explorer限制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。你不能使用裸XML,或者裸JSON,任何数据,不管什么格式,必须在一个回调函数之中被组装起来。
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,而不是做为字符串必须被进一步处理。正由于如此,它多是客户端上获取并解析数据最快的方法。咱们比较了动态脚本标签插入和XHR的性能,在本章后面JSON 一节中。
请当心使用这种技术从你不能直接控制的服务器上请求数据。JavaScript没有权限或访问控制的概念,因此你的页面上任何使用动态脚本标签插入的代码均可以彻底控制整个页面。包括修改任何内容、将用户重定向到另外一个站点,或跟踪他们在页面上的操做并将数据发送给第三方。使用外部来源的代码时务必很是当心。
3、多部分XHR
多部分XHR(MXHR)容许你只用一个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=array(′kitten.jpg′,′sunset.jpg′,′baby.jpg′);foreach(images as image)$imagefh=fopen($image,′r′);$imagedata=fread($imagefh,filesize($image));fclose($imagefh);$payloads[]=base64encode($imagedata);image)$imagefh=fopen($image,′r′);$imagedata=fread($imagefh,filesize($image));fclose($imagefh);$payloads[]=base64encode($imagedata);newline = chr(1);
echo implode(newline,newline,payloads);
这段PHP代码读取三个图片,并将它们转换成base64字符串。它们之间用一个简单的字符,UNICODE的1,链接起来,而后返回给客户端。
而后回到客户端,此数据由splitImage 函数处理:
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);
}
}
此函数将拼接而成的字符串分解为三段。每段用于建立一个图像元素,而后将图像元素插入页面中。图像不是从base64 转换成二进制,而是使用data:URL 并指定image/jpeg 媒体类型。
最终结果是:在一次HTTP 请求中向浏览器传入了三张图片。也能够传入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响应报文愈来愈大,有必要在每一个资源收到时马上处理,而不是等待整个响应报文接收完成。这能够经过监听readyState3 实现:
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;
}
当readyState3第一次发出时,启动了一个定时器。每隔15毫秒检查一次响应报文中的新数据。数据片断被收集起来直到发现一个分隔符,而后一切都做为一个完整的资源处理。以健壮的方式使用MXHR的代码很复杂但值得进一步研究。
使用此技术有一些缺点,其中最大的缺点是以此方法得到的资源不能被浏览器缓存。若是你使用MXHR获取一个特定的CSS 文件而后在下一个页面中正常加载它,它不在缓存中。由于整批资源是做为一个长字符串传输的,而后由JavaScript代码分割。因为没有办法用程序将文件放入浏览器缓存中,因此用这种方法获取的资源也没法存放在那里。
另外一个缺点是:老版本的Internet Explorer不支持readyState3或data: URL。Internet Explorer 8两个都支持,但在Internet Explorer 6和7中必须设法变通。
尽管有这些缺点,但某些状况下MXHR仍然显著提升了总体页面的性能:网页包含许多其余地方不会用到的资源(因此不须要缓存),尤为是图片。
网站为每一个页面使用了独一无二的打包的JavaScript或CSS文件以减小HTTP请求,由于它们对每一个页面来讲是独一的,因此不须要从缓存中读取,除非从新载入特定页面。
因为HTTP请求是Ajax中最极端的瓶颈之一,减小其需求数量对整个页面性能有很大影响。尤为是当你将100个图片请求转化为一个MXHR请求时。Ad hoc 在现代浏览器上测试了大量图片,其结果显示出此技术比逐个请求快了4到10倍。
有时你不关心接收数据,而只要将数据发送给服务器。你能够发送用户的非私有信息以备往后分析,或者捕获全部脚本错误而后将有关细节发送给服务器进行记录和提示。当数据只需发送给服务器时,有两种普遍应用的技术:XHR和灯标。
(1) XMLHttpRequest
虽然XHR主要用于从服务器获取数据,它也能够用来将数据发回。数据能够用GET或POST 方式发回,以及任意数量的HTTP 信息头。这给你很大灵活性。当你向服务器发回的数据量超过浏览器的最大URL长度时XHR特别有用。这种状况下,你能够用POST 方式发回数据:
var url = '/data.php';
var params = [
'id=934875',
'limit=20'
];
var req = new XMLHttpRequest();
req.onerror = function() {
// Error.
};
req.onreadystatechange = function() {
if (req.readyState== 4) {
// Success.
}
};
req.open('POST', url, true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setRequestHeader('Content-Length', params.length);
req.send(params.join('&'));
正如你在这个例子中看到的,若是失败了咱们什么也不作。当咱们用XHR捕获登录用户统计信息时这么作一般没什么问题,可是,若是发送到服务器的是相当重要的数据,你能够添加代码在失败时重试:
function xhrPost(url, params, callback) {
var req = new XMLHttpRequest();
req.onerror = function() {
setTimeout(function() {
xhrPost(url, params, callback);
}, 1000);
};
req.onreadystatechange = function() {
if (req.readyState== 4) {
if (callback && typeof callback === 'function') {
callback();
}
}
};
req.open('POST', url, true);
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setRequestHeader('Content-Length', params.length);
req.send(params.join('&'));
}
当使用XHR将数据发回服务器时,它比使用GET要快。这是由于对少许数据而言,向服务器发送一个GET请求要占用一个单独的数据包。另外一方面,一个POST至少发送两个数据包,一个用于信息头。另外一个用于POST体。POST更适合于向服务器发送大量数据,即由于它不关心额外数据包的数量,又由于Internet Explorer 的URL长度限制,它不可能使用过长的GET请求。
(2) 灯标
此技术与动态脚本标签插入很是相似。JavaScript用于建立一个新的Image 对象,将src 设置为服务器上一个脚本文件的URL。此URL 包含咱们打算经过GET格式传回的键值对数据。注意并无建立img 元素或者将它们插入到DOM 中。
var url = '/status_tracker.php';
var params = [
'step=2',
'time=1248027314'
];
(new Image()).src = url + '?' + params.join('&');
服务器取得此数据并保存下来,而没必要向客户端返回什么,所以没有实际的图像显示。这是将信息发回服务器的最有效方法。其开销很小,并且任何服务器端错误都不会影响客户端。
简单的图像灯标意味着你所能作的受到限制。你不能发送POST 数据,因此你被URL 长度限制在一个至关小的字符数量上。你能够用很是有限的方法接收返回数据。能够监听Image 对象的load 事件,它能够告诉你服务器端是否成功接收了数据。你还能够检查服务器返回图片的宽度和高度(若是返回了一张图片)并用这些数字通知你服务器的状态。例如,宽度为1 表示“成功”,2 表示“重试”。
若是你不须要为此响应返回数据,那么你应当发送一个204 No Content 响应代码,无消息正文。它将阻止客户端继续等待永远不会到来的消息体:
var url = '/status_tracker.php';
var params = [
'step=2',
'time=1248027314'
];
var beacon = new Image();
beacon.src = url + '?' + params.join('&');
beacon.onload = function() {
if (this.width == 1) {
// Success.
}
else if (this.width == 2) {
// Failure; create another beacon and try again.
}
};
beacon.onerror = function() {
// Error; wait a bit, then create another beacon and try again.
};
灯标是向服务器回送数据最快和最有效的方法。服务器根本不须要发回任何响应正文,因此你没必要担忧客户端下载数据。惟一的缺点是接收到的响应类型是受限的。若是你须要向客户端返回大量数据,那么使用XHR。若是你只关心将数据发送到服务器端(可能须要极少的回复),那么使用图像灯标。php