蓝狮注册JavaScript之条件表达式性能影响

与循环相似,条件表达式决定JavaScript运行流的走向。其他语言使用if-else或者switch表达式的传统观点也适用于JavaScript。由于不同的浏览器针对流程控制进行了不同的优化,使用哪种技术并不总是很清楚。
一、if-else 与switch 比较
使用if-else或者switch的流行理论是基于测试条件的数量:蓝狮官网条件数量较大,倾向于使用switch而不是if-else。这通常归结到代码的易 读性。这种观点认为,如果条件较少时,if-else容易阅读,而条件较多时switch更容易阅读。考虑下面几点:
if (found){
//do something
} else {
//do something else
}

switch(found){
case true:
//do something
break;
default:
//do something else
}
虽然两个代码块实现同样任务,蓝狮注册很多人会认为if-else 表达式比witch 表达式更容易阅读。如果增加条件体的数量,通常会扭转这种观点:
if (color == “red”){
//do something
} else if (color == “blue”){
//do something
} else if (color == “brown”){
//do something
} else if (color == “black”){
//do something
} else {
//do something
}
switch (color){
case “red”:
//do something
break;
case “blue”:
//do something
break;
case “brown”:
//do something
break;
case “black”:
//do something
break;
default:
//do something
}
大多数人会认为这段代码中的switch 表达式比if-else 表达式可读性更好。事实证明,大多数情况下switch表达式比if-else更快,但只有当条件体数量很大时才明显更快。两者间的主要性能区别在于:当 条件体增加时,if-else 性能负担增加的程度比switch 更多。因此,我们的自然倾向认为条件体较少时应使用if-else 而条件体较多时应使用switch 表达式,如果从性能方面考虑也是正确的。
一般来说,if-else 适用于判断两个离散的值或者判断几个不同的值域。如果判断多于两个离散值,switch表达式将是更理想的选择。
二、优化if-else
优化if-else 的目标总是最小化找到正确分支之前所判断条件体的数量。最简单的优化方法是将最常见的条件体放在首位。考虑下面的例子:
if (value < 5) { //do something } else if (value > 5 && value < 10) {
//do something
} else {
//do something
}
这段代码只有当value值经常小于5 时才是最优的。如果value 经常大于等于10,那么在进入正确分支之前,必须两次运算条件体,导致表达式的平均时间提高。if-else中的条件体应当总是按照从最大概率到最小概率 的顺序排列,以保证理论运行速度最快。
另外一种减少条件判断数量的方法是将if-else组织成一系列嵌套的if-else表达式。使用一个单独的一长串的if-else通常导致运行缓慢,因为每个条件体都要被计算。例如:
if (value == 0){
return result0;
} else if (value == 1){
return result1;
} else if (value == 2){
return result2;
} else if (value == 3){
return result3;
} else if (value == 4){
return result4;
} else if (value == 5){
return result5;
} else if (value == 6){
return result6;
} else if (value == 7){
return result7;
} else if (value == 8){
return result8;
} else if (value == 9){
return result9;
} else {
return result10;
}
在这个if-else 表达式中,所计算条件体的最大数目是10。如果假设value 的值在0 到10 之间均匀分布,那么会增加平均运行时间。为了减少条件判断的数量,此代码可重写为一系列嵌套的if-else 表达式,例如:
if (value < 6){
if (value < 3){
if (value == 0){
return result0;
} else if (value == 1){
return result1;
} else {
return result2;
}
} else {
if (value == 3){
return result3;
} else if (value == 4){
return result4;
} else {
return result5;
}
}
} else {
if (value < 8){
if (value == 6){
return result6;
} else {
return result7;
}
} else {
if (value == 8){
return result8;
} else if (value == 9){
return result9;
} else {
return result10;
}
}
}
在重写的if-else表达式中,每次抵达正确分支时最多通过四个条件判断。它使用二分搜索法将值域分成了一系列区间,然后逐步缩小范围。当数值范围分布 在0到10时,此代码的平均运行时间大约是前面那个版本的一半。此方法适用于需要测试大量数值的情况(相对离散值来说switch更合适)。
三、查表法
有些情况下要避免使用if-else或switch。当有大量离散值需要测试时,if-else和switch都比使用查表法要慢得多。在 JavaScript中查表法可使用数组或者普通对象实现,查表法访问数据比if-else或者switch更快,特别当条件体的数目很大时。
与if-else和switch相比,查表法不仅非常快,而且当需要测试的离散值数量非常大时,也有助于保持代码的可读性。例如,当switch 表达式很大时就变得很笨重,诸如:
switch(value){
case 0:
return result0;
case 1:
return result1;
case 2:
return result2;
case 3:
return result3;
case 4:
return result4;
case 5:
return result5;
case 6:
return result6;
case 7:
return result7;
case 8:
return result8;
case 9:
return result9;
default:
return result10;
}
switch 表达式代码所占的空间可能与它的重要性不成比例。整个结构可以用一个数组查表替代:
//define the array of results
var results = [result0, result1, result2, result3, result4, result5, result6, result7, result8, result9, result10]
//return the correct result
return results[value];
当使用查表法时,必须完全消除所有条件判断。操作转换成一个数组项查询或者一个对象成员查询。使用查表法的一个主要优点是:由于没有条件判断,当候选值数量增加时,很少,甚至没有增加额外的性能开销。
查表法最常用于一个键和一个值形成逻辑映射的领域(如前面的例子)。一个switch 表达式更适合于每个键需要一个独特的动作,或者一系列动作的场合。
四、递归
复杂算法通常比较容易使用递归实现。事实上,有些传统算法正是以递归实现的,诸如阶乘函数:
function factorial(n){
if (n == 0){
return 1;
} else {
return n * factorial(n-1);
}
}
递归函数的问题是,一个错误定义,或者缺少终结条件可导致长时间运行,冻结用户界面。此外,递归函数还会遇到浏览器调用栈大小的限制。
五、调用栈限制
JavaScript 引擎所支持的递归数量与JavaScript 调用栈大小直接相关。只有Internet Explorer 例外,它的调用栈与可用系统内存相关,其他浏览器有固定的调用栈限制。大多数现代浏览器的调用栈尺寸比老式浏览器要大(例如Safari 2 调用栈尺寸是100)。当你使用了太多的递归,超过最大调用栈尺寸时,浏览器会出错并弹出以下信息:
1、Internet Explorer: “Stack overflow at line x”
2、Firefox: “Too much recursion”
3、Safari: “Maximum call stack size exceeded”
4、Opera: “Abort (control stack overflow)”
Chrome 是唯一不显示调用栈溢出错误的浏览器。
关于调用栈溢出错误,最令人感兴趣的部分大概是:在某些浏览器中,他们的确是JavaScript错误,可
以用一个try-catch表达式捕获。异常类型因浏览器而不同。在Firefox中,它是一个InternalError;在Safari
和Chrome中,它是一个RangeError;在Internet Explorer中抛出一个一般性的Error 类型。(Opera不抛出
错误;它终止JavaScript 引擎)。这使得我们能够在JavaScript中正确处理这些错误:
try {
recurse();
} catch (ex){
alert(“Too much recursion!”);
}
如果不管它,那么这些错误将像其他错误一样冒泡上传(在Firefox中,它结束于Firebug和错误终端;在Safari/Chrome中它显示在 JavaScript终端上),只有Internet Explorer例外。IE 不会显示一个JavaScript错误,但是会弹出一个提示堆栈溢出信息的对话框。

0 Comments
Leave a Reply