ajax 是高性能javascript的基石 可以通过延迟下载大量资源是页面加载更快 通过客户端和服务端异步传送数据 避免页面集体加载 局部刷新页面 通过选择正确的传输技术和最有效的数据格式 可以改善和用户的体验。
一 数据传输
五种常用技术向服务器请求数据:
XMLHttpRequest
Dynamic script tag insertion 动态脚本标签插入
iframes
Comet
Multipart XHR
XMLHttpRequest XHR :
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(); // Get the response headers.
var data = req.responseText; // Get the data.
// Process the data here...
}
}
req.open('GET', url + '?' + params.join('&'), true);
req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // Set a request header.
req.send(null); // Send the request.
其中 req.readyState === 3 表示正在和服务器交互 响应报文 还在传输中 所谓的流 但是老版本的浏览器不支持流
缺点是处理大数据比较慢
使用get还是post方式:
如果只是请求不改变服务器的状态只是取回数据(幂等动作)使用get get请求被缓冲起来 如果是多次提取相同的数据可提高性能 但当Url和参数的长度超过2048个字符最好使用post IE限制Url长度 过长会被截短
动态脚本标签插入:
克服了xhr的最大限制 可以从不同域的服务器上获取数据。
var scriptElement = document.createElement('script');
scriptElement.src = 'http://any-domain.com/javascript/lib.js';
document.getElementsByTagName_r('head')[0].appendChild(scriptElement);
不能通过请求发送信息头 参数只能是get方式不能post 不能设置请求超时和重试 不知道是否失败 必须等数据返回之后才可以访问。
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 + ')');
// Process the data here...
}
多部分 XHR (MXHR):允许一个http请求获取服务器多个资源。
简单的例子:
var req = new XMLHttpRequest();
req.open('GET', 'rollup_images.php', true);
req.onreadystatechange = function() {
if (req.readyState == 4) {
splitImages(req.responseText);
}
};
req.send(null);
---------------------------------------
简单的图片处理
$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);
}
// Roll up those strings into one long string and output it.
$newline = chr(1); // This character won't appear naturally in any base64 string.
echo implode($newline, $payloads);
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);
}
}
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) {
// Start polling.
getLatestPacketInterval = window.setInterval(function() {
getLatestPacket();
}, 15);
}
if (req.readyState === 4) {
// Stop polling.
clearInterval(getLatestPacketInterval);
// Get the last packet.
getLatestPacket();
}
}
function getLatestPacket() {
var length = req.responseText.length;
var packet = req.responseText.substring(lastLength, length);
processPacket(packet);
lastLength = length;
}
缺点不支持缓存。
网页包含许多其他地方不会用到的资源(所以不需要缓存) 尤其是图片 可以减少javascript和css文件http请求。
post方式重试:
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('&'));
}
Beacons 灯标
传送数据格式:
XML:必须提前知道详细结构 如何拼装成javascript对象
例子:
<?xml version="1.0" encoding='UTF-8'?>
<users total="4">
<user id="1">
<username>alice</username>
<realname>Alice Smith</realname>
<email>alice@alicesmith.com</email>
</user>
<user id="2">
<username>bob</username>
<realname>Bob Jones</realname>
<email>bob@bobjones.com</email>
</user>
<user id="3">
<username>carol</username>
<realname>Carol Williams</realname>
<email>carol@carolwilliams.com</email>
</user>
<user id="4">
<username>dave</username>
<realname>Dave Johnson</realname>
<email>dave@davejohnson.com</email>
</user>
</users>
解析:
function parseXML(responseXML) {
var users = [];
var userNodes = responseXML.getElementsByTagName_r('users');
var node, usernameNodes, usernameNode, username,
realnameNodes, realnameNode, realname,
emailNodes, emailNode, email;
for (var i = 0, len = userNodes.length; i < len; i++) {
node = userNodes[i];
username = realname = email = '';
usernameNodes = node.getElementsByTagName_r('username');
if (usernameNodes && usernameNodes[0]) {
usernameNode = usernameNodes[0];
username = (usernameNodes.firstChild) ?
usernameNodes.firstChild.nodeValue : '';
}
realnameNodes = node.getElementsByTagName_r('realname');
if (realnameNodes && realnameNodes[0]) {
realnameNode = realnameNodes[0];
realname = (realnameNodes.firstChild) ?
realnameNodes.firstChild.nodeValue : '';
}
emailNodes = node.getElementsByTagName_r('email');
if (emailNodes && emailNodes[0]) {
emailNode = emailNodes[0];
email = (emailNodes.firstChild) ?
emailNodes.firstChild.nodeValue : '';
}
users[i] = {
id: node.getAttribute('id'),
username: username,
realname: realname,
email: email
};
}
return users;
}
更有效的方式将每个值存储为user标签的属性 这样文件尺寸更小
<?xml version="1.0" encoding='UTF-8'?>
<users total="4">
<user id="1-id001" username="alice" realname="Alice Smith" email="alice@alicesmith.com" />
<user id="2-id001" username="bob" realname="Bob Jones" email="bob@bobjones.com" />
<user id="3-id001" username="carol" realname="Carol Williams" email="carol@carolwilliams.com" />
<user id="4-id001" username="dave" realname="Dave Johnson" email="dave@davejohnson.com" />
</users>
解析:
function parseXML(responseXML) {
var users = [];
var userNodes = responseXML.getElementsByTagName_r('users');
for (var i = 0, len = userNodes.length; i < len; i++) {
users[i] = {
id: userNodes[i].getAttribute('id'),
username: userNodes[i].getAttribute('username'),
realname: userNodes[i].getAttribute('realname'),
email: userNodes[i].getAttribute('email')
};
}
return users;
}
XPath 数据格式:在分析xml文档时比getElementsByTagName快得多 但是没有得到广泛支持 必须使用老式的DOM遍历写代码
JSON 数据格式:轻量级并易于解析的数据格式 它是按照javascript对象和数组字面语法所编写的
[
{"id":1, "username":"alice", "realname": "Alice Smith", "email":"alice@alicesmith.com"},
{"id":2, "username":"bob", "realname": "Bob Jones", "email":"bob@bobjones.com"},
{"id":3, "username":"carol", "realname": "Carol Williams","email":"carol@carolwilliams.com"},
{"id":4, "username":"dave", "realname": "Dave Johnson", "email":"dave@davejohnson.com"}
]
function parseJSON(responseText) {
return ('(' + responseText + ')');
}
改造更简单的版本:
[
{ "i": 1, "u": "alice", "r": "Alice Smith", "e": "alice@alicesmith.com" },
{ "i": 2, "u": "bob", "r": "Bob Jones", "e": "bob@bobjones.com" },
{ "i": 3, "u": "carol", "r": "Carol Williams", "e": "carol@carolwilliams.com" },
{ "i": 4, "u": "dave", "r": "Dave Johnson", "e": "dave@davejohnson.com" }
]
解析:
function parseJSON(responseText) {
var users = [];
var usersArray = ('(' + responseText + ')');
for (var i = 0, len = usersArray.length; i < len; i++) {
users[i] = {
id: usersArray[i][0],
username: usersArray[i][1],
realname: usersArray[i][2],
email: usersArray[i][3]
};
}
return users;
}
三中json格式的性能:Array JSON》Simple JSON 》Verbose JSON
JSON-P 数据格式:
当使用动态脚本标签插入时 JSON数据被视为另一个javascript文件并作为本地代码执行 为做到这一点 数据必须被包装在回调函数中 这就是所谓的 JSon填充 json_p
能够跨域使用
parseJSON([
{"id":1, "username":"alice", "realname":"Alice Smith", "email":"alice@alicesmith.com"},
{"id":2, "username":"bob", "realname":"Bob Jones", "email":"bob@bobjones.com"},
{"id":3, "username":"carol", "realname":"Carol Williams", "email":"carol@carolwilliams.com"},
{"id":4, "username":"dave", "realname":"Dave Johnson", "email":"dave@davejohnson.com"}
]);
HTML数据格式:
document.getElementById('data-container').innerHTML = req.responseText;
自定义格式:
多维数组格式split()分割解析
function parseCustomFormat(responseText) {
var users = [];
var usersEncoded = responseText.split(';');
var userArray;
for (var i = 0, len = usersEncoded.length; i < len; i++) {
userArray = usersEncoded[i].split(':');
users[i] = {
id: userArray[0],
username: userArray[1],
realname: userArray[2],
email: userArray[3]
};
}
return users;
}
手动缓存:
var localCache = {};
function xhrRequest(url, callback) {
// Check the local cache for this URL.
if (localCache[url]) {
callback.success(localCache[url]);
return;
}
// If this URL wasn't found in the cache, make the request.
var req = createXhrObject();
req.onerror = function() {
callback.error();
};
req.onreadystatechange = function() {
if (req.readyState == 4) {
if (req.responseText === '' || req.status == '404') {
callback.error();
return;
}
// Store the response on the local cache.
localCache[url] = req.responseText;
callback.success(req.responseText);
}
};
req.open("GET", url, true);
req.send(null);
}
了解 ajax 的限制:
如果要用到流功能必须监听readyState 3 在一个大的响应报文没有完全接收之前就开始解析它 这样就可以实时处理报文片段 这也就是MXhr提高性能的原因 但是大多数javascript库不允许你访问readystatechange事件。
function createXhrObject() {
var msxml_progid = [
'MSXML2.XMLHTTP.6.0',
MSXML3.XMLHTTP',
'Microsoft.XMLHTTP', // Doesn't support readyState 3.
'MSXML2.XMLHTTP.3.0', // Doesn't support readyState 3.
];
var req;
try {
req = new XMLHttpRequest(); // Try the standard way first.
}
catch(e) {
for (var i = 0, len = msxml_progid.length; i < len; ++i) {
try {
req = new ActiveXObject(msxml_progid[i]);
break;
}
catch(e2) { }
}
}
finally {
return req;
}
}