《高性能JavaScript》读书笔记⑦:Ajax

  • 为了避免页面资源一窝蜂地下载,Ajax提供异步的方式在客户端和服务器端之间传输数据。
  • Ajax是高性能JavaScript的基础,它甚至可以只用一个HTTP请求就获得整个页面的资源。只要我们选择合适和传输方式和最有效的数据格式,可以显著改善用户的交互体验。

数据传输(Data Transmission)

  • 从最基本的层面来说,Ajax实际上是一种与服务器通信而无须重载页面的方法。
  • 同时,也有多种方法建立这种通信通道,而不同的方法有各自的优点和限制。
数据请求(Requesting Data)
  • 页面向服务器请求数据,常用的有五种:

    • XMLHttpRequest(XHR)
    • Dynamic script tag insertion 动态脚本注入
    • iframes
    • Comet
    • Multipart XHR

    (加粗字体的是现代JavaScript常用的技术,iframesComet是极端情况下使用,这里不做讨论)

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

  • 当我们需要发送用户操作日志或者需要记录脚本错误时,我们并不关心接受数据,只关系数据发送给服务器。此时有两种广泛使用的技术:XHRbeacons(信标)。
  • 虽然XHR主要用于从服务器获取数据,但同样可以通过POST方式把数据发送服务器。
  • 案例:用XHR发送用户统计信息
var url = '/data.php';
var params = ['id=640202047','limit20'];
var req = new XMLHttpRequest();
req.onreadystatechange = function(){
   if(req.readyState == 4){
      //成功
   }
};
//处理发送失败的情况
req.onerror = 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
  • 使用图片信标的方式向服务器发送数据,是最快捷且最有效的。它的性能消耗极小,服务端的错误完全不会影响客户端。(如果需要处理其错误的情况,也可以通过onloadonerror事件来监听)

数据格式(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.onerror = 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类库,可能会遇到古怪的浏览器兼容问题)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容