《高性能JavaScript》读书笔记⑦:Ajax
为了避免页面资源一窝蜂地下载,Ajax提供异步的方式在客户端和服务器端之间传输数据。
Ajax是高性能JavaScript的基础,它甚至可以只用一个HTTP请求就获得整个页面的资源。只要我们选择合适和传输方式和最有效的数据格式,可以显著改善用户的交互体验。
数据传输(Data Transmission)
从最基本的层面来说,Ajax实际上是一种与服务器通信而无须重载页面的方法。
同时,也有多种方法建立这种通信通道,而不同的方法有各自的优点和限制。
数据请求(Requesting Data)
页面向服务器请求数据,常用的有五种:
(加粗字体的是现代JavaScript常用的技术,
iframes
和Comet
是极端情况下使用,这里不做讨论)XMLHttpRequest(XHR)
Dynamic script tag insertion 动态脚本注入
iframes
Comet
Multipart XHR
XMLHttpRequest(XHR)是目前实现异步发送和接收数据最常用的技术。所有主流浏览器都提供了完善的支持。
<script> var url = '/data.php'; var params = ['id=640202047','limit20']; var req = new XMLHttpRequest(); req.onreadystatechange = function(){ if(req.readyState == 4){ //获取响应头信息 var reqsponseHeaders = req.getAllResponseHeaders(); //获取数据 var data = req.responseText; } } req.open('GET',url+'?'+params.join('&',true)); //设置请求头信息 req.setRequestHeader('X-Requested-With','XMLHttpRequest'); //发送请求 req.send(null);</script>
浏览器对XHR进行跨域限制,不能通过XHR从外域请求数据。
XHR的POST和GET请求的选择:对于那些不改变服务器状态,只获取数据的请求,应该使用GET;但请求的URL长度接近或超过2048个字符时,应该选择POST请求。
动态脚本注入能够跨域请求数据,其原理就是使用JavaScript创建一个新的脚本标签,设置它的src属性为不同域的URL。
var scriptElement = document.createElement('script'); scriptElement.src = 'http://any-domain.com/javascript/lib.js';document.getElementsByTagName('head')[0].appendChild(scriptElement);function jsonCallback(jsonString){ var data = eval('(' + jsonString + ')'); }
jsonCallback({'status':1,"colors":['#fff','#000','#ff0000']});
动态脚本注入的方式也有一些短板,不能合作请求的头信息,只能通过GET方式,不能设置请求的超时处理或重试,并且可能带来安全性问题。
multipart XHR(MXHR)是一项新技术,运行客户端只用一个HTTP请求就可以从服务端获取多个资源。
multipart XHR的原理是将资源(CSS/HTML/JavaScript/image)打包成一个由双方约定格式的长字符串来传输,然后通过JavaScript根据
mime-type
类型来解析每个资源。
var req = new XMLHttpRequest();var getLatesPacketInterval,lastLength = 0; req.open('GET','rollup_image.php',true); req.openreadystatechange = readyStateHander; req.send(null);function readyStateHander(){ if(req.readyState == 3){ //开始轮询 getLatestPacketInterval = window.setInterval(function(){ getLaesPacket(); },15); } if(req.readyState == 4){ //停止轮询 clearInterval(getLatestPacketInterval); //获取最后一个数据包 getLaesPacket(); } }function getLaesPacket(){ var length = req.responseText.length; var packet = req.responseText.substring(lastLength,length); processPacket(packet); lastLenght = length; }
当readySate
状态为3时,启动一个定时器,每15毫秒检查一次响应中的新数据。数据片段会被收集起来,直到发现一个分隔符,然后就把遇到分隔符之前的数据作为一个完整的资源进行处理。(编写健壮的MXHR代码比较复杂,但却值得研究)
MXHR最大的缺点就是以此种方式获取的数据不能被浏览器缓存,比如通过MXHR获取的CSS和图片文件无法被缓存。
发送数据(Sending Data)
当我们需要发送用户操作日志或者需要记录脚本错误时,我们并不关心接受数据,只关系数据发送给服务器。此时有两种广泛使用的技术:
XHR
和beacons
(信标)。虽然XHR主要用于从服务器获取数据,但同样可以通过
POST
方式把数据发送服务器。案例:用XHR发送用户统计信息
var url = '/data.php';var params = ['id=640202047','limit20'];var req = new XMLHttpRequest(); req.onreadystatechange = function(){ if(req.readyState == 4){ //成功 } };//处理发送失败的情况req. = fucntion(){ setTimeout(arguments.callee,1000); }; req.open('POST',url+'?'+params.join('&',true));//设置请求头信息req.setRequestHeader('Content-Type','application/x-www-form-urlencoded'); req.setRequestHeader('Content-Length',params.length);//发送请求req.send(param.join('&'));
Beacons非常类似动态脚本注入,即用JavaScript创建一个Image对象,把
src
属性设置为要请求的URL,URL中包含要上送的键值对数据。
var url = '/status_tracker.php';var params = ['id=640202047','limit20']; (new Image()).src = url + '?' + param.join(‘&’);//注意:创建Image元素即可,不需要插入DOM
使用图片信标的方式向服务器发送数据,是最快捷且最有效的。它的性能消耗极小,服务端的错误完全不会影响客户端。(如果需要处理其错误的情况,也可以通过
和
事件来监听)
数据格式(DataS Formats)
选择数据格式取决于具体的用途,不同的数据格式有不同的优势,有的解析更快,有点传输更快。
当Ajax开始流行时,选择了XML作为数据格式。XML结构严谨,具有极佳的通用性,几乎所有的服务端语言都有支持XML操作的类库。但XML比起其他格式更冗长,每个数据片段都依赖大量结构。而在JavaScript中,解析XML需要花费相当大的精力。
function parseXml(responseXML){ var users = []; var userNodes = responseXML.getElementsByName('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 = ''; //解析用户名 usernameNode = node.getElementsByTagName('username'); if(usernameNode && usernameNodes[0]){ usernameNode = usernameNodes[0]; username = (usernameNodes.firstChild())? usernameNodes.firstChild():''; } //解析本名 realnameNodes = node.getElementsByTagName('realname'); if(realnameNode && realnameNodes[0]){ realnameNode = realnameNodes[0]; realname = (realnameNodes.firstChild())? realnameNodes.firstChild():''; } //解析邮箱地址 emailNodes = node.getElementsByTagName('email'); if(emailNode && emailNodes[0]){ emailNode = emailNodes[0]; email = (emailNodes.firstChild())? emailNodes.firstChild():''; } //放入数组 user[i] = { id : node.getAtrribute('id'), username : username, realname : realname, email : email }; } }
另外,通过
XPath
可以更方便的解析XML文档。JSON
是一种使用JavaScript对象和数组字面量编写的,轻量级且易于维护的数据格式。在JavaScript中,可以通过
eval()
方法进行解析JSON字符串
function parseXml(responseText){ var users = []; var userArray = eval('(' + responseText + ')'); for (var i=0,len=userArray.length;i<len;i++) { users[i] = { id : userArray[i][0], username : userArray[i][1], realname : userArray[i][2], email : userArray[i][3] } } }
和XML相比JSON有着更小的传输尺寸,解析速度更快。对于Web开发人员来说,JSON是高性能Ajax的基础,尤其是使用动态脚本注入时。
通常请求的数据需要被转换成HTML以显示到页面上,而转换的操作在服务器处理会快得多,所以有一种可考虑的技术,就是在服务器上构建好整个HTML再传回客户端。JavaScript再直接通过
innerHTML
属性插入到页面响应的位置。
document.getElementById('data-container').innerHTML = req.responseText;
但HTML是一种臃肿的数据格式,HTML标签可以相互嵌套,每个标签还有id、class等属性,甚至比XML更繁杂,同时导致传输数据量很大。
除此之外,hi可以自定义数据格式。案例给出一个以分隔符组成的列表。
1:alice:Alice Smith:alice@alicesmih.com; 2:bob:Bob Jones:bob@bobjones.com; 3.dave:Dave Johnson:dave@daejohnson.com;
function parseXml(responseText){ var users = []; var userEncoded = responseText.split(';'); var userArray; for (var i=0,len=userEncoded .length;i<len;i++) { userArray = userEncoded[i].split(':'); users[i] = { id : userArray[i][0], username : userArray[i][1], realname : userArray[i][2], email : userArray[i][3] }; } }
Ajax性能指南(Ajax Performance Guidelines)
当选择了合适的数据传输技术和数据格式后,就该开始考虑其他的优化技术了。
缓存数据(Cache Data)
“最快的请求,就是没有请求”,我们通过两种方式避免不必要的请求:
在服务端,设置HTTP头信息以确保你的响应被浏览器缓存;
在客户端,把响应的数据存储到本地,避免再次请求;
除了使用GET方式发送请求之外,还需要设置正确的HTTP头信息。指定
Expires
信息是最简单的方式,指明浏览器应该缓存响应多久,而无须改变客户端的任何代码。除了依赖浏览器处理缓存外,还可以手动实现请求的缓存。
var localCache = {}; //缓存集合var url = '/data.php';function xhrRequest(url,callback){ //检查本地缓存的URL if(localCache[url]){ callback.success(localCache[url]); return; } var req = createHhrObject(); req. = fucntion(){ callback.error(); }; req.onreadystatechange = function(){ if(req.readyState == 4){ if(req.responseText === ''|| req.status == '404'){ callback.error(); } //存储响应文本到本地缓存 localCache[url] = req.responseText; callback.success(req.responseText); } }; req.open('GET',url,true); req.send(null); }
了解Ajax类库的局限(Know the Limitations of Your Ajax Library)
本来Ajax对象屏蔽了浏览器之间的差异是件好事,但却使得开发者不能访问XMLHttpRequest的完整功能。比如,监听
readyState
为3的状态,可以在一个较大的响应还没有完全接收之间把它分段处理,可以实时处理响应片段。还可以访问不同的readyStates状态,直接操作XHR对象减少函数开销,进一步提升了性能。 (注意:但放弃使用Ajax类库,可能会遇到古怪的浏览器兼容问题)
作者:梁同学de自言自语
链接:https://www.jianshu.com/p/1a2ec4222cc5
共同学习,写下你的评论
评论加载中...
作者其他优质文章