首页 » 编写高质量代码:改善JavaScript程序的188个建议 » 编写高质量代码:改善JavaScript程序的188个建议全文在线阅读

《编写高质量代码:改善JavaScript程序的188个建议》建议21:推荐提高循环性能的策略

关灯直达底部

每次运行循环体时都会产生性能开销,增加总的运行时间,即使是循环体中最快的代码,累计迭代上千次,也将带来不小的负担。因此,减少循环的迭代次数可获得显著的性能提升。例如:


var iterations=Math.floor(items.length/8),startAt=items.length%8,i=0;

do{

switch(startAt){

case 0:

process(items[i++]);

case 7:

process(items[i++]);

case 6:

process(items[i++]);

case 5:

process(items[i++]);

case 4:

process(items[i++]);

case 3:

process(items[i++]);

case 2:

process(items[i++]);

case 1:

process(items[i++]);

}

startAt=0;

}while(--iterations);


在上面代码中,每次循环最多可调用process函数8次。循环迭代次数为元素总数除以8。因为总数不一定是8的整数倍,所以startAt变量存放余数,指明第一次循环中应当执行多少次process。如果现在有12个元素,那么第一次循环将调用process4次,第二次循环调用process8次,用两次循环代替了12次循环。在下面的代码中取消switch表达式,将余数处理与主循环分开。


var i=items.length%8;

while(i){

process(items[i--]);

}

i=Math.floor(items.length/8);

while(i){

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

process(items[i--]);

}


虽然上面代码使用两个循环替代了先前的一个循环,但是在循环体中去掉了switch表达式,速度更快。如果循环的迭代次数少于1000次,减少迭代次数的循环与普通循环相比可能只有微不足道的性能提升。如果迭代次数超过1000次,比如在500 000次迭代中,合理地减少循环的迭代次数可以使运行时间减少到普通循环的70%。

有两个因素影响到循环的性能:

❑每次迭代干什么。

❑迭代的次数。

通过减少这两者中的一个或全部(的执行时间),可以提高循环的整体性能。如果一次循环需要较长时间来执行,那么多次循环将需要更长时间。限制在循环体内进行耗时操作的数量是一个加快循环的好方法。一个典型的数组处理循环可采用3种循环的任何一种。最常用的代码如下:


//方法1

for(var i=0;i<items.length;i++){

process(items[i]);

}

//方法2

var j=0;

while(j<items.length){

process(items[j++]);

}

//方法3

var k=0;

do{

process(items[k++]);

}while(k<items.length);


在每个循环中,每次运行循环体都要发生如下操作:

第1步,在控制条件中读一次属性(items.length)。

第2步,在控制条件中执行一次比较(i<items.length)。

第3步,比较操作,观察条件控制体的运算结果是不是true(i<items.length==true)。

第4步,一次自加操作(i++)。

第5步,一次数组查找(items[i])。

第6步,一次函数调用(process(items[i]))。

在这些简单的循环中,即使没有太多的代码,每次迭代都要进行这6步操作。代码运行速度很大程度上由process对每个项目的操作所决定,即便如此,减少每次迭代中操作的总数也可以大幅度提高循环的整体性能。

优化循环的第一步是减少对象成员和数组项查找的次数。在大多数浏览器上,这些操作比访问局部变量或直接量需要更长时间。例如,在上面代码中,每次循环都查找items.length,这是一种浪费,因为该值在循环体执行过程中不会改变,因此产生了不必要的性能损失。我们可以简单地将此值存入一个局部变量中,在控制条件中使用这个局部变量,从而提高了循环性能,例如:


for(var i=0,len=items.length;i<len;i++){

process(items[i]);

}

var j=0,count=items.length;

while(j<count){

process(items[j++]);

}

var k=0,num=items.length;

do{

process(items[k++]);

}while(k<num);


这些重写后的循环只在循环执行之前对数组长度进行一次属性查询,使控制条件中只有局部变量参与运算,因此速度更快。根据数组的长度,在大多数浏览器上总循环时间可以节省大约25%,在IE浏览器中可节省50%。

还可以通过改变循环的顺序来提高循环性能。通常,数组元素的处理顺序与任务无关,可以从最后一个开始,直到处理完第一个元素。倒序循环是编程语言中常用的性能优化方法,不过一般不太容易理解。在JavaScript中,倒序循环可以略微提高循环性能,例如:


for(var i=items.length;i--;){

process(items[i]);

}

var j=items.length;

while(j--){

process(items[j]);

}

var k=items.length-1;

do{

process(items[k]);

}while(k--);


在上面代码中使用了倒序循环,并且在控制条件中使用了减法。每个控制条件只是简单地与零进行比较。控制条件与true值进行比较,任何非零数字自动强制转换为true,而零等同于false。实际上,控制条件已经从两次比较减少到一次比较。将每次迭代中两次比较减少到一次可以大幅度提高循环速度。通过倒序循环和最小化属性查询,可以看到执行速度比原始版本提升了50%~60%。

与原始版本相比,每次迭代中只进行如下操作:

第1步,在控制条件中进行一次比较(i==true)。

第2步,一次减法操作(i--)。

第3步,一次数组查询(items[i])。

第4步,一次函数调用(process(items[i]))。

新循环的每次迭代中减少两个操作,随着迭代次数的增长,性能将显著提升。