第4步,一次自加操作(i++)。
第5步,一次数组查找(items[i])。
第6步,一次函数调用(process(items[i]))。
在这些简单的循环中,即使没有太多的代码,每次迭代都要进行这6步操作。
代码运行速度很大程度上由process()对每个项目的操作所决定,即便如此,减少每次迭代中操作的总数也可以大幅度提高循环的整体性能。
优化循环的第一步是减少对象成员和数组项查找的次数。
在大多数浏览器上,这些操作比访问局部变量或直接量需要更长时间。
例如,在上面代码中,每次循环都查找items.length,这是一种浪费,因为该值在循环体执行过程中不会改变,因此产生了不必要的性能损失。
我们可以简单地将此值存入一个局部变量中,在控制条件中使用这个局部变量,从而提高了循环性能,例如:
1.for (var i=0, len=items.length; i < len; i++){
2. process(items[i]);
3.}
4.var j=0, count = items.length;
5.while (j < count){
6. process(items[j++]);
7.}
8.var k=0, num = items.length;
9.do {
10. process(items[k++]);
11.} while (k < num);
这些重写后的循环只在循环执行之前对数组长度进行一次属性查询,使控制条件中只有局部变量参与运算,因此速度更快。
根据数组的长度,在大多数浏览器上总循环时间可以节省大约25%,在IE浏览器中可节省50%。
还可以通过改变循环的顺序来提高循环性能。
通常,数组元素的处理顺序与任务无关,可以从最后一个开始,直到处理完第一个元素。
倒序循环是编程语言中常用的性能优化方法,不过一般不太容易理解。
在JavaScript中,倒序循环可以略微提高循环性能,例如:
1.for (var i=items.length; i--; ){
2. process(items[i]);
3.}
4.var j = items.length;
5.while (j--){
6. process(items[j]);
7.}
8.var k = items.length-1;
9.do {
10. process(items[k]);
11.} while (k--);
在上面代码中使用了倒序循环,并且在控制条件中使用了减法。
每个控制条件只是简单地与零进行比较。
控制条件与true值进行比较,任何非零数字自动强制转换为true,而零等同于false。
实际上,控制条件已经从两次比较减少到一次比较。
将每次迭代中两次比较减少到一次可以大幅度提高循环速度。
通过倒序循环和最小化属性查询,可以看到执行速度比原始版本提升了50%~60%。
与原始版本相比,每次迭代中只进行如下操作:
第1步,在控制条件中进行一次比较(i==true)。
第2步,一次减法操作(i--)。
第3步,一次数组查询(items[i])。
第4步,一次函数调用(process(items[i]))。
新循环的每次迭代中减少两个操作,随着迭代次数的增长,性能将显著提升。
建议22:
少用函数迭代
ECMA-262v4为本地数组对象新增加了一个forEach方法。
此方法遍历一个数组的所有成员,并且在每个成员上执行一个函数。
在每个元素上执行的函数作为forEach()的参数传进去,并在调用函数时接收3个参数:
数组项的值、数组项的索引、数组自身。
例如:
1.items.forEach(function(value, index, array){
2. process(value);
3.});
forEach在Firefox、Chrome和Safari等浏览器中为原生函数。
另外,forEach在大多数JavaScript库中都有等价实现,例如:
1.//YUI 3
2.Y.Array.each(items, function(value, index, array){
3. process(value);
4.});
5.//jQuery
6.jQuery.each(items, function(index, value){
7. process(value);
8.});
9.//Dojo
10.dojo.forEach(items, function(value, index, array){
11. process(value);
12.});
13.//Prototype
14.items.each(function(value, index){
15. process(value);
16.});
尽管基于函数的迭代使用起来非常便利,但是比基于循环的迭代要慢一些。
每个数组项要关联额外的函数调用是造成速度慢的主要原因。
在所有情况下,基于函数的迭代占用时间是基于循环的迭代的8倍,因此在非特殊需求下,不建议使用函数迭代。
建议23:
推荐提高条件性能的策略
与循环相似,条件表达式决定JavaScript运行流的走向。
与其他语言一样,JavaScript也采用了if和switch两种条件结构。
由于不同浏览器针对流程控制进行了不同的优化,因此两者在性能上并没有特别大的差异,主要还是根据需求形式进行分析和选择:
条件数量较大,建议选择switch结构,而不是if结构,这样可以使代码更易读;如果条件较少时,建议选择if结构。
1.//条件少
2.if(found) {
3. //执行代码
4.} else {
5. //执行代码
6.}
7.//条件多
8.switch (color) {
9. case "red":
10. //执行代码
11. break;
12. case "blue":
13. //执行代码
14. break;
15. case "brown":
16. //执行代码
17. break;
18. case "black":
19. //执行代码
20. break;
21. default:
22. //执行代码
23.}
事实证明,在大多数情况下,switch比if运行更快,但是只有当条件体数量很大时才明显更快。
switch与if的主要性能区别在于:
当条件体增加时,if性能负担增加的程度比switch更大。
因此,从性能方面考虑,如果条件体较少,应使用if;如果条件体较多,应使用switch。
一般来说,if适用于判断两个离散的值或几个不同的值域。
如果判断多于两个离散值,那么switch将是更理想的选择。
建议24:
优化if逻辑
(1)
逻辑顺序体现了人的思维的条理性和严密性。
合理的顺序可以提升解决问题的品质,相反,混乱的顺序很容易导致各种错误的发生。
在分支结构中经常需要面临各种优化逻辑顺序的问题。
人在思考问题时,一般总会对各种最可能发生的情况做好准备,这叫做“有备而来”。
分支结构中各种条件根据情况的先后、轻重来排定顺序。
如果把最可能的条件放在前面,把最不可能的条件放在后面,这样程序在执行时总会按照代码先后顺序逐一检测所有条件,直到发现匹配的条件时才停止继续检测。
如果把最可能的条件放在前面,就等于降低了程序的检测次数,自然也就提升了分支结构的执行效率,避免空转,这在大批量数据检测中效果非常明显。
例如,对于一个论坛系统来说,普通会员的数量要远远大于版主和管理员的数量。
换句话说,大部分登录的用户都是普通会员,如果把普通会员的检测放在分支结构的前面,就会减少计算机检测的次数。
if优化目标:
最小化找到正确分支之前所判断条件体的数量。
if优化方法:
将最常见的条件体放在首位。
例如:
1.if(value < 5) {
2. //do something
3.} else if(value > 5 && value < 10) {
4. //do something
5.} else {
6. //do something
7.}
这段代码只有在value值经常小于5时才是最优的。
如果value经常大于或等于10,那么在进入正确分支之前,必须两次运算条件体,导致表达式的平均运行时间增加。
if中的条件体应当总是按照从最大概率到最小概率的顺序排列,以保证理论运行速度最快。
另外一种减少条件判断数量的方法:
将if编写成一系列嵌套结构。
使用一个单独的一长串的if结构通常导致运行缓慢,因为每个条件体都要被计算,例如:
1.if(value == 0) {
2. return result0;
3.} else if(value == 1) {
4. return result1;
5.} else if(value == 2) {
6. return result2;
7.} else if(value == 3) {
8. return result3;
9.} else if(value == 4) {
10. return result4;
11.} else if(value == 5) {
12. return result5;
13.} else if(value == 6) {
14. return result6;
15.} else if(value == 7) {
16. return result7;
17.} else if(value == 8) {
18. return result8;
19.} else if(value == 9) {
20. return result9;
21.} else {
22. return result10;
23.}
在这个if结构中,所计算的条件体的最大数目是10。
如果假设value的值在0~10之间均匀分布,那么会增加平均运行时间。
为了减少条件判断的数量,可重写为一系列嵌套结构,例如:
1.if(value < 6) {
2. if(value < 3) {
3. if(value == 0) {
4. return result0;
5. } else if(value == 1) {
6. return result1;
7. } else {
8. return result2;
9. }
10. } else {
11. if(value == 3) {
12. return result3;
13. } else if(value == 4) {
14. return result4;
15. } else {
16. return result5;
17. }
18. }
19.} else {
20. if(value < 8) {
21. if(value == 6) {
22. return result6;
23. } else {
24. return result7;
25. }
26. } else {
27. if(value == 8) {
28. return result8;
29. } else if(value == 9) {
30. return result9;
31. } else {
32. return result10;
33. }
34. }
35.}
建议24:
优化if逻辑
(2)
重写if结构后,每次抵达正确分支时最多通过4个条件判断。
新的if结构使用二分搜索法将值域分成了一系列区间,然后逐步缩小范围。
当数值范围分布在0~10时,此代码的平均运行时间大约是前面代码的一半。
此方法适用于需要测试大量数值的情况,而相对离散值来说switch更合适。
当然,在性能影响不是很大的情况下,遵循条件检测的自然顺序会更容易阅读。
例如,对于检测周一到周五值日任务安排的分支结构来说,虽然周五的任务比较重要,但是这类任务有着明显的顺序,安排顺序结构还是遵循它的自然逻辑比较好。
如果打乱条件的顺序,把周五的任务安排在前面,对于整个分支结构的执行性能没有太大的帮助,并且不方便阅读代码。
考虑到这一点,按自然顺序来安排结构会更富可读性。
应注意分支之间的顺序优化,当然在同一个条件表达式内部也应该考虑逻辑顺序问题。
在执行逻辑“与”或逻辑“或”运算时,有可能会省略右侧表达式的计算,如果希望不管右侧表达式是否成立都进行计算,就应该考虑逻辑顺序问题。
例如,有两个条件a和b,其中条件a多为true,b就是一个必须执行的表达式,那么下面的逻辑顺序设计就欠妥当:
1.if(a && b){
2.}
如果条件a为false,则JavaScript会忽略表达式b的计算。
如果b表达式影响到后面的运算,那么不执行表达式b自然会对后面的逻辑产生影响,这时可以采用下面的逻辑结构,在if结构前先执行表达式b,这样即使条件a的返回值为false,也能够保证表达式b被计算。
1.var c = b;
2.if(a && b){
3.}
建议25:
恰当选用if和switch
switch结构中存在很多限制,存在这些限制的主要目的是提高多重分支结构的执行效率。
因此,如果能够使用switch结构,就不要选择if结构。
无论是使用if结构,还是使用switch结构,应该确保下面3个目标的基本实现:
准确表现事物内在的、固有的逻辑关系。
不能为了结构而破坏事物的逻辑关系。
优化逻辑的执行效率。
执行效率是程序设计的重要目标,不能为了省事而随意耗费资源。
简化代码的结构层次,使代码更方便阅读。
相对来说,下面几种情况更适合使用switch结构:
枚举表达式的值。
这种枚举是可以期望的、平行逻辑关系的。
表达式的值具有离散性,不具有线性的非连续的区间值。
表达式的值是固定的,不是动态变化的。
表达式的值是有限的,而不是无限的,一般情况下表达式应该比较少。
表达式的值一般为整数、字符串等类型的数据。
而if结构则更适合下面的一些情况:
具有复杂的逻辑关系。
表达式的值具有线性特征,如对连续的区间值进行判断。
表达式的值是动态的。
测试任意类型的数据。
例如,针对学生分数进行不同的判断,如果分数小于60,则评定为不及格;如果分数在60~75(不包含75)之间,则评定为合格;如果分数在75~85(不包含85)之间,则评定为良好;如果分数在85~100之间,则评定为优秀。
针对这种情况,表达式的值是连续的线性判断,显然使用if结构会更合适一些。
1.if(score < 60){
2. alert("不及格");
3.}
4.else if(60 <= score < 75){
5. alert("合格");
6.}
7.else if(75 <= score < 85){
8. alert("良好");
9.}
10.else if(85 <= score <= 100){
11. alert("优秀");
12.}
如果使用switch结构,则需要枚举100种可能,如果分数值还包括小数,情况就更加复杂了,此时使用switch结构就不是明智之举。
但是,对于有限制的枚举数据,比如性别,使用switch结构会更高效,例如:
1.switch(sex){
2. case "女":
3. alert("女士");
4. break;
5. case "男":
6. alert("先生");
7. break;
8. default:
9. alert("请选择性别");
10.}
建议26:
小心if嵌套的思维陷阱
人的思维是非常复杂的,这在一定程度上会增加if结构嵌套的复杂性。
假设有4个条件,只有当这些条件全部成立时,才允许执行某件事情。
遵循人的一般思维习惯,在检测这些条件时,常常会沿用下面这种结构嵌套:
1.if(a){
2. if(b){
3. if(c){
4. if(d){
5. alert("所有条件都成立!
");
6. }
7.