核心导读:在服装管理软件开发应用过程中,特别对于使用B/S架构开发POS系统,提高系统运行速度是关键。使用javascript是提高性能方式之一

 
很久就想总结一下关于javascript性能优化方面的一些东西,平时也有注意收集这方面的资料。把del.icio.us里的收藏的东西翻出来看一遍,才惊奇地发现,这些所谓的优化方法大多出自《javascript高级程序设计》一书,当然也有个别

不一样的。总之这本书上关于javascript性能优化的内容足足用了近20页来进行阐述,所以今天我也照本宣科地来介绍一下,同时收录其他方法。

javascript的性能优化应分为两部分:下载时间和执行时间。

下载时间
javascript作为一种解释性语言不同于其他编程语言。在诸如java,c,c++的语言中,开发人员根本无需考虑变量名的长度以及长篇大论的注释,因为这些在编译时都会被删除。

但是web浏览器下载的是javascript的源代码,你编写的javascript程序文件会原模原样地下载到客户端。而这些长变量名和注释就会影响脚本的下载时间。单个TCP-IP包中能放入的字节数是1160,所以最好将每个javascript文件的大小控制在1160字节以内以获取最优的下载时间。

javascript文件中的任何字节,不管是空格,还是换行都会影响javascript文件的下载时间,所以在部署任何javascript文件之前,都应该尽可能地优化脚本文件的体积。以下是一些常用的优化javascript文件大小的方法:

1、删除注释

脚本中的任何注释都应该在部署之前被删除。虽然注释对于开发人员来说意义重大,但在部署时这些注释对客户端用户是没有任何实质意义的,而更可怕的是很多javascript源代码中的注释会比代码多得多。因此删除注释是所见javascript文件大小最为方便有效的途径。

2、删除制表符和空格

又规律地缩进代码有增强代码的可阅读性,但是浏览器/客户端用户并不需要这些额外的制表符和可个,所以最好删除它们,包括函数参数,赋值语句和比较操作符之间的空格:
function doSomething ( arg1, arg2, arg3 ){
 alert(arg1 + arg2 + arg3);
}
function doSomething(arg1,arg2,arg3){alert(arg1+arg2+arg3);}
3、删除所有换行

和前一条规则一样,只要你在程序的每行结尾都正确地添加了分号,那么就不需要再添加换行符了。

4、替换变量名

此方法很无聊,大致做法就是将所有变量名替换成无意义的简短变量名。

要手动地完成以上四步,在实际中可能会有一定的难度,那么下面的一些链接可能对你有所帮助。

ECMAScript Cruncher:http://saltstorm.net/depo/esc/

JSMin(The JavaScript Minifier): http://www.crockford.com/javascript/jsmin.html

Online JavaScript Compressor:http://dean.edwards.name/packer/

5、替换布尔值

对于比较来说,true等译1,false等于0。因此可以将字面量的true都用1来替换,而false用0来替换,进而较少一定的字节数。

6、缩短否定检测

代码中常常会出现检测某个值时候有效的语句。而大部分否定测试所做的就是判断某个变量是否为undefined、null或者false,如下:
if(oTest != undefined){
 //dosomething
}
if(oTest != null){
 //dosomething
}
if(oTest != false){
 //dosomething
}
//这样写更简洁
if(!oTest){
 //dosomethin
}
7、使用数组和对象变量
var aTest = new Array;
var oTest = new Object;
oTest.pro1 = "pro1"
oTest.pro2 = "pro2";
和下面的代码作用是完全相同的,但是下面的代码可以节省更多的字节。
var aTest = [];
var oTest = {pro1:"pro1",pro2:"pro2"};

执行时间

javascript是一种解释性语言,它的执行速度要大大慢于编译型语言。据测试,javascript的执行效率要比编译型的C程序慢5000倍;比解释型的Java慢100倍;比解释型的Perl慢10倍。但是我们仍然可以通过一些简单的事情来提高javascript的效率,同时这也显得更加重要。

1、使用局部变量

在函数中,总是使用var来定义变量。无论何时使用var都会在当前的范围类创建一个局部变量。如果不使用var来定义变量,那么变量会被创建在window范围内,那么每次使用这个变量的时候,解释程序都会搜索整个范围树。同时全局变量要在页面从浏览器中卸载后才销毁,而局部变量在函数执行完毕即可销毁,过多的全局变量增加了不必要的内存消耗。

2、避免使用with语句

使用with语句能够减少一定的代码长度,但是在使用with语句时,要强制解释程序不仅在范围树内查找局部变量,还强制检测每个变量及指定的对象,看其是否为特性。因为,我们也可以在函数中定义同明的变量。

3、选择正确的算法

只要有可能就应该用局部变量或者数字索引的数组来替代命名特性。如果命名特性要多次使用,就先将它的值存储在局部变量中,避免多次使用线性算法请求命名特性的值。
var aValues = [1,2,3,4,5,6,7];
function testFunc(){
 for(var i=0, iCount=aValues.length; i++){
  alert(i + "/" + iCount + "=" + aValues[i]);
 }
}
4、反转循环

循环在各种编程语言中得到大量应用,所以保持循环的高效性尤为重要。按照反向的顺序进行循环迭代是一种有效的方法。
for(var i=aValues.length-1; i >= 0; i--){
 //do something
}
反转循环有利于减低算法的复杂度。它用常数0作为循环控制语句以减小执行时间。

5、翻转循环

用do..while循环来替代while循环以进一步减少执行时间。假设有如下while循环:
var i=0;
 while(i < aValues.length){
  //do something
 i++;
}
可用do..while循环重写上面的代码而不改变行为:
var i=0;
do{
 //do something
 i++;
}while(i < aValues.length)

这段代码比用while循环更快,因为它用循环反转来进一步地优化:
var i=aValues.length-1;
do{
 //do something
 i--;
}while(i>=0)
也可以将自减操作直接放进控制语句中,以减少额外的语句。
var i=aValues.length-1;
do{
 //do somethin
}while(--i>=0)
这个循环已经被完全优化了

6、展开循环

可以考虑将循环展开,一次执行多个语句。考虑如下for循环例子:
var sum = 0;var aValues=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
for(var i=0; i<aValues.length; i++){
 sum+=aValues[i];
}

循环总共要执行20次,每次都是对变量sum进行增量操作,但是可以这样写:
var aValues=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
var sum = 0;
for(var i=0; i<aValues.length; i++){
 sum+=aValues[i];
 i++;
 sum+=aValues[i];
 i++;
 sum+=aValues[i];
 i++;
 sum+=aValues[i];
 i++;
 sum+=aValues[i];
 i++;
}

在循环体内做了五次增量。每次增量后,都对变量i加1,所以多数组的遍历与原来是一致的,但是控制语句只执行了四次,减少了执行时间。当然还可以继续优化:
var aValues=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
var sum = 0;
for(var i=0; i<aValues.length; i++){
 sum+=aValues[i++];
 sum+=aValues[i++];
 sum+=aValues[i++];
 sum+=aValues[i++];
 sum+=aValues[i++];
}
关于此算法的更多信息可以参考这里http://home.earthlink.net/~kendrasg/info/js_opt/

7、优化if语句

使用if语句和多个else语句时,一定要把最有可能的情况放在第一个,然后是第二可能出现的情况,如此排列,这样就减少了要进行多次测试才能遇到正确条件的情况。

同时也要尽量减少else if语句的数量,并且将条件按照二叉树的方式进行排列。例如:
if(iNum>0){
if(iNum>10){
 alert("Between 0 and 10");
}else{
 if(iNum>20){
  alert("Between 10 and 20");
 }else{
  if(iNum<30){
   alert("Between 20 and 30");
  }else{
   alert("Greater than or equal to 30");
  }
 }
}
}else{
 alert("Less than or equal to 0");
}

8、switch和if

一般来说超过两种情况时,最好使用switch语句。常用switch来代替if语句,最高可令执行快10倍。在javascript中就更加可以从中获益,因为case语句可以使用任何类型的值。

9、避免字符串连接

一旦一次要使用多个字符串的连接(比如,大于五个),最好使用如下方式:
var buf = new Array();
for(var i = 0; i < 100; i++){
 buf.push(i.toString());
}
var all = buf.join("");
10、优先使用内置方法

只要可能,就应该考虑优先使用内置方法。因为内置方法是用C++或者C之类的语言编译的,运行起来比必须实时编译的javascript要高效的多。比如你可能像要自己编写一个求阶乘的函数,但是实际上你应该使用javascript内置的Math.pow()方法。

11、存储常用的值

当多次使用到一个值得时候,可先将其存储在局部变量中以便快速访问。尤其对于通常使用对象的特性来进行访问的值更加重要。如:
oDiv1.style.left = document.body.clientWidth;
oDiv2.style.left = document.body.clientWidth;

document.body.clientWidth在该例中被使用了两次,但它是使用命名特性(属于极其昂贵的操作)来获取的。可以使用局部变量来重写这段代码:
var iClientWidth = document.body.clientWidth;
oDiv1.style.left = iClientWidth;
oDiv2.style.left = iClientWidth;

12、最小化语句数量

我们有理由相信,脚本中语句越少,执行所需要的时间越短。很多方法可以将javascript中的语句数量减到最少,比如定义变量时,处理迭代数字时,使用数组和对象字面量时。

多个变量的定义可用一个var语句来替代:
var iFive = 5, sColor = "red", aValues = [1,2,3], oDate = new Date();

插入迭代子

使用迭代子(在不同位置上加减的值)时,尽可能合并语句。考虑下面代码:
var sName = aValues[i];
i++;
这样写更简短:
var sName = aValues[i++];

13、节约使用DOM

不管是添加、删除或者是其他对页面DOM内部结构的更改,都会导致整个页面的重新渲染,带来的是明显的时间消耗。解决这个问题的方法是尽可能地对不在DOM文档中的元素节点进行操作。如下例子:
var oUL = document.getElementById("ulItems");
for(var i=0; i<10; i++){
 var oLI = document.createElement("li");
 oUL.appendChild(oLI); //I
 oLI.appendChild(document.createTextNode("Item "+i)); //II
}

每次执行I都会对整个页面重新渲染一次,执行II又会对整个页面重新渲染一次,总共会有20次的对页面的渲染。我们大可使用文档碎片来保存所有列表项,最后再一起添加到文档中。
var oUL = document.getElementById("ulItems");
var oFragment = document.createDocumentFragment();
for(var i=0; i<10; i++){
 var oLI = document.createElement("li");
 oLI.appendChild(document.createTextNode("Item "+i));
 oFragment.appendChild(oLI);
}
oUL.appendChild(oFragment);
这样对文档中DOM树的操作就只有一次。

其他一些优化方法

1、不要使用eval

使用eval相当于在运行时再次调用解释引擎对内容进行运行,需要消耗大量时间。这时候使用JavaScript所支持的闭包可以实现函数模版(关于闭包的内容请参考函数式编程的有关内容)

2、类型转换

类型转换是大家常犯的错误,因为JavaScript是动态类型语言,你不能指定变量的类型。
  • 把数字转换成字符串,应用”" + 1,虽然看起来比较丑一点,但事实上这个效率是最高的,性能上来说:(“” + ) > String() > .toString() > new String()这条其实和下面的“直接量”有点类似,尽量使用编译时就能使用的内部操作要比运行时使用的用户操作要快。String()属于内部函数,所以速度很快,而.toString()要查询原型中的函数,所以速度逊色一些,new String()用于返回一个精确的副本。
  • 浮点数转换成整型,这个更容易出错,很多人喜欢使用parseInt(),其实parseInt()是用于将字符串转换成数字,而不是浮点数和整型之间的转换,我们应该使用Math.floor()或者Math.round()。另外,和第二节的对象查找中的问题不一样,Math是内部对象,所以Math.floor()其实并没有多少查询方法和调用的时间,速度是最快的。
  • 对于自定义的对象,如果定义了toString()方法来进行类型转换的话,推荐显式调用toString(),因为内部的操作在尝试所有可能性之后,会尝试对象的toString()方法尝试能否转化为String,所以直接调用这个方法效率会更高
3、字符串遍历操作

对字符串进行循环操作,譬如替换、查找,应使用正则表达式,因为本身JavaScript的循环速度就比较慢,而正则表达式的操作是用C写成的语言的API,性能很好。

4、DOM相关
  • 插入HTML 很多人喜欢在JavaScript中使用document.write来给页面生成内容。事实上这样的效率较低,如果需要直接插入HTML,可以找一个容器元素,比如指定一个div或者span,并设置他们的innerHTML来将自己的HTML代码插入到页面中。
  • 创建节点 尽量避免只是使用html字符串来创建节点。因为这样做既无法保证代码的有效性,同时字符串的操作效率有极低。所以应该是用document.createElement()方法,而如果文档中存在现成的样板节点,应该是用cloneNode()方法,因为使用createElement()方法之后,你需要设置多次元素的属性,使用cloneNode()则可以减少属性的设置次数——同样如果需要创建很多元素,应该先准备一个样板节点。
  • 定时器 如果针对的是不断运行的代码,不应该使用setTimeout,而应该是用setInterval。setTimeout每次要重新设置一个定时器