原文转载自:http://dorado.group.iteye.com/group/topic/7229java
在原文基础上,本人(如下出现的笔者,均为原做者)作了一些内容修改和格式调整。node
AJAX的出现极大地改变了Web应用客户端的操做模式,它使得用户能够在全心工做时,没必要频繁的忍受那使人厌恶的页面刷新。理论上AJAX技术在很大的程度上能够减小用户操做的等待时间,同时节约网络上的数据流量。然而,实际状况却并不老是这样。用户时常会抱怨用了AJAX的系统,响应速度反而下降了。web
笔者从事AJAX方面的研发多年,参与开发了目前国内较为成熟的AJAX平台-dorado。根据笔者的经验,致使这种结果的根本缘由并不在AJAX。不少时候系统响应速度的下降都是由不够合理的界面设计和不够高效的编程习惯形成的。下面咱们就来分析几个AJAX开发过程当中须要时刻注意的环节。ajax
1.合理的使用客户端编程和远程过程调用。 客户端的编程主要都是基于JavaScript的,而JavaScript是一种解释型的编程语言,它的运行效率相对于Java等都要稍逊一筹,同时JavaScript又是运行在浏览器这样一个严格受限的环境当中。所以开发人员对于哪些逻辑能够在客户端执行应该有一个清醒的认识。 2.尽量避免频繁的使用远程过程调用,例如避免在循环体中使用远程过程调用。 3.若是可能的话尽量使用AJAX方式的远程过程调用(异步方式的远程过程调用)。 4.避免将重量级的数据操做放置在客户端。例如:大批量的数据复制操做、须要经过大量的数据遍历完成的计算等。 5.改进对DOM对象的操做方式。 6.提升字符串累加的速度 7.避免DOM对象的内存泄漏 8.复杂页面的分段装载和初始化 9.利用GZIP压缩网络流量
客户端的编程中,对DOM对象的操做每每是最容易占用CPU时间的,并且不一样的编程方法之间的性能差别又每每是很是大的。算法
如下是三段运行结果彻底相同的代码,它们的做用是在网页中建立一个10x1000的表格。然而它们的运行速度却有着天壤之别。
编程
/* 测试代码1 - 耗时: 41秒*/ var table = document.createElement("TABLE"); document.body.appendChild(table); for(var i = 0; i < 1000; i++){ var row = table.insertRow(-1); for(var j = 0; j < 10; j++){ var cell = objRow.insertCell(-1); cell.innerText = "( " + i + " , " + j + " )"; } } /* 测试代码2 - 耗时: 7.6秒 */ var table = document.getElementById("TABLE"); document.body.appendChild(table); var tbody = document.createElement("TBODY"); table.appendChild(tbody); for(var i = 0; i < 1000; i++){ var row = document.createElement("TR"); tbody.appendChild(row); for(var j = 0; j < 10; j++){ var cell = document.createElement("TD"); row.appendChild(cell); cell.innerText = "( " + i + " , " + j + " )"; } } /* 测试代码3 - 耗时: 1.26秒 */ var tbody = document.createElement("TBODY"); for(var i = 0; i < 1000; i++){ var row = document.createElement("TR"); for(var j = 0; j < 10; j++){ var cell = document.createElement("TD"); cell.innerText = "( " + i + " , " + j + " )"; row.appendChild(cell); } tbody.appendChild(row); } var table = document.getElementById("TABLE"); table.appendChild(tbody); document.body.appendChild(table);
这里的“测试代码1”和“测试代码2”之间的差异在于在建立表格单元时使用了不一样的API方法,而“测试代码2”和“测试代码3” 之间的差异在于处理顺序的略微不一样。浏览器
“测试代码1”和“测试代码2”之间如此大的性能差异咱们无从分析,目前所知的是insertRow和insertCell是DHTML中表格特有的API,createElement和appendChild是W3C DOM的原生API。而前者应该是对后者的封装。不过,咱们并不能所以而得出结论认为DOM的原生API老是优于对象特有的API。建议你们在须要频繁调用某一API时,对其性能表现作一些基本的测试。性能优化
“测试代码2”和“测试代码3”之间的性能差别主要来自于他们的构建顺序不一样。“测试代码2”的作法是首先建立最外层的《TABLE》对象,而后再在循环中依次建立《TR>和《TD》。而“测试代码3”的作法是首先在内存中由内到外的构建好整个表格,最后再将它添加到网页中。这样作的目的是尽量的减小浏览器从新计算页面布局的次数。每当咱们将一个对象添加到网页中时,浏览器都会尝试对页面中的控件的布局进行从新计算。因此,若是咱们可以首先在内存中将整个要构造的对象所有建立好,而后再一次性的添加到网页中。那么,浏览器将只会作一次布局的重计算。总结为一句话那就是越晚执行appendChild越好。有时为了提升运行效率,咱们甚至能够考虑先使用removeChild将已存在的控件从页面中移除,而后构造完成后再从新将其放置回页面当中。网络
对于字符串累加的问题闭包
在使用AJAX提交信息时,我可能经常须要拼装一些比较大的字符串经过XmlHttp来完成POST提交。尽管提交这样大的信息的作法看起来并不优雅,但有时咱们可能不得不面对这样的需求。那么JavaScript中对字符串的累加速度如何呢?咱们先来作下面的这个实验。累加一个长度为30000的字符串。
/* 测试代码1 - 耗时: 14.325秒 */ var str = ""; for (var i = 0; i < 50000; i++) { str += "xxxxxx"; } 这段代码耗时14.325秒,结果并不理想。如今咱们将代码改成以下的形式: /* 测试代码2 - 耗时: 0.359秒 */ var str = ""; for (var i = 0; i < 100; i++) { var sub = ""; for (var j = 0; j < 500; j++) { sub += "xxxxxx"; } str += sub; } 这段代码耗时0.359秒!一样的结果,咱们作的只是首先拼装一些较小的字符串而后再组装成更大的字符串。这种作法能够有效的在字符串拼装的后期减少内存复制的数据量。知道了这一原理以后咱们还能够把上面的代码进一步拆散之后进行测试。下面的代码仅耗时0.140秒。 /* 测试代码3 - 耗时: 0.140秒 */ var str = ""; for (var i1 = 0; i1 < 5; i1++) { var str1 = ""; for (var i2 = 0; i2 < 10; i2++) { var str2 = ""; for (var i3 = 0; i3 < 10; i3++) { var str3 = ""; for (var i4 = 0; i4 < 10; i4++) { var str4 = ""; for (var i5 = 0; i5 < 10; i5++) { str4 += "xxxxxx"; } str3 += str4; } str2 += str3; } str1 += str2; } str += str1; }
不过,上面这种作法也许并非最好的!若是咱们须要提交的信息是XML格式的(其实绝大多数状况下,咱们均可以设法将要提交的信息组装成XML格式),咱们还能找到更高效更优雅的方法—利用DOM对象为咱们组装字符串。下面这段代买组装一个长度为950015的字符串仅须耗时0.890秒。
/* 利用DOM对象组装信息 - 耗时: 0.890秒 */ var xmlDoc; if (browserType == BROWSER_IE) { xmlDoc = new ActiveXObject("Msxml.DOMDocument"); } else { xmlDoc = document.createElement("DOM"); } var root = xmlDoc.createElement("root"); for (var i = 0; i < 50000; i++) { var node = xmlDoc.createElement("data"); if (browserType == BROWSER_IE) { node.text = "xxxxxx"; } else { node.innerText = "xxxxxx"; } root.appendChild(node); } xmlDoc.appendChild(root); var str; if (browserType == BROWSER_IE) { str = xmlDoc.xml; } else { str = xmlDoc.innerHTML; }
对于DOM对象的内存泄漏的问题
关于IE中DOM对象的内存泄露是一个经常被开发人员忽略的问题,然而它带来的后果倒是很是严重的!它会致使IE的内存占用量持续上升,而且浏览器的总体运行速度明显降低。对于一些泄露比较严重的网页,甚至只要刷新几回,运行速度就会下降一倍。
比较常见的内存泄漏的模型有“循环引用模型”、“闭包函数模型”和“DOM插入顺序模型”,对于前两种泄漏模型,咱们均可以经过在网页析构时解除引用的方式来避免,而对于“DOM插入顺序模型”则须要经过改变一些惯有的编程习惯的方式来避免。
复杂页面的分段装载和初始化
对系统当中某些确实比较复杂而又不便使用IFrame的界面,咱们能够对其实施分段装载。例如对于多页标签的界面,咱们能够首先下载和初始化多页标签的默认页,而后利用AJAH(asynchronous JavaScript and HTML)技术来异步的装载其余标签页中的内容。这样就能保证界面能够在第一时间首先展示给用户。把整个复杂界面的装载过程分散到用户的操做过程中。
利用GZIP压缩网络流量
除了上面提到的这些代码级的改良以外,咱们还能够利用GZIP来有效的下降网络流量。目前常见的主流浏览器已经所有支持GZIP算法,咱们每每只须要编写少许的代码就能够支持GZIP了。例如在J2EE中咱们能够在Filter中经过下面的代码来判断客户端浏览器是否支持GZIP算法,而后根据须要利用java.util.zip.GZIPOutputStream来实现GZIP的输出。
/* 判断浏览器对GZIP支持方式的代码 */ private static String getGZIPEncoding(HttpServletRequest request) { String acceptEncoding = request.getHeader("Accept-Encoding"); if (acceptEncoding == null) return null; acceptEncoding = acceptEncoding.toLowerCase(); if (acceptEncoding.indexOf("x-gzip") >= 0) return "x-gzip"; if (acceptEncoding.indexOf("gzip") >= 0) return "gzip"; return null; }
通常而言,GZIP对于HTML、JSP的压缩比能够达到80%左右,而它形成的服务端和客户端的性能损耗几乎是能够忽略的。结合其余因素,支持GZIP的网站有可能为咱们节约50%的网络流量。所以GZIP的使用能够为那些网络环境不是特别好的应用带来显著的性能提高。
使用Http的监视工具Fiddler能够方便的检测出网页在使用GZIP先后的通信数据量。Fiddler的下载地址是http://www.fiddlertool.com/fiddler/
关于Web应用的性能优化实际上是一个很是大的话题。本文因为篇幅有限,只能涉及其中的几个细节,而且也没法将这些细节的优化方式全面的展示给你们。指望本文可以引发你们对Web应用尤为是客户端性能优化的充分重视,并且在客户端的方法改进每每可以获得使人惊奇的性能提高。