[翻译]High Performance JavaScript(013)

Conditionals  条件表达式

 

    Similar in nature to loops, conditionals determine how execution flows through JavaScript. The traditional argument of whether to use if-else statements or a switch statement applies to JavaScript just as it does to other languages. Since different browsers have implemented different flow control optimizations, it is not always clear which technique to use.

    与循环相似,条件表达式决定JavaScript运行流的走向。其他语言使用if-else或者switch表达式的传统观点也适用于JavaScript。由于不同的浏览器针对流程控制进行了不同的优化,使用哪种技术并不总是很清楚。

 

if-else Versus switch  if-else与switch比较

 

    The prevailing theory on using if-else versus switch is based on the number of conditions being tested: the larger the number of conditions, the more inclined you are to use a switch instead of if-else. This typically comes down to which code is easier to read. The argument is that if-else is easier to read when there are fewer conditions and switch is easier to read when the number of conditions is large. Consider the following:

    使用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
}

    Though both pieces of code perform the same task, many would argue that the if-else statement is much easier to read than the switch. Increasing the number of conditions, however, usually reverses that opinion:

    虽然两个代码块实现同样任务,很多人会认为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
}

 

    Most would consider the switch statement in this code to be more readable than the if-else statement.

    大多数人会认为这段代码中的switch表达式比if-else表达式可读性更好。

 

    As it turns out, the switch statement is faster in most cases when compared to if-else, but significantly faster only when the number of conditions is large. The primary difference in performance between the two is that the incremental cost of an additional condition is larger for if-else than it is for switch. Therefore, our natural inclination to use if-else for a small number of conditions and a switch statement for a larger number of conditions is exactly the right advice when considering performance.

    事实证明,大多数情况下switch表达式比if-else更快,但只有当条件体数量很大时才明显更快。两者间的主要性能区别在于:当条件体增加时,if-else性能负担增加的程度比switch更多。因此,我们的自然倾向认为条件体较少时应使用if-else而条件体较多时应使用switch表达式,如果从性能方面考虑也是正确的。

 

    Generally speaking, if-else is best used when there are two discrete values or a few different ranges of values for which to test. When there are more than two discrete values for which to test, the switch statement is the most optimal choice.

    一般来说,if-else适用于判断两个离散的值或者判断几个不同的值域。如果判断多于两个离散值,switch表达式将是更理想的选择。

 

Optimizing if-else  优化if-else

 

    When optimizing if-else, the goal is always to minimize the number of conditions to evaluate before taking the correct path. The easiest optimization is therefore to ensure that the most common conditions are first. Consider the following:

    优化if-else的目标总是最小化找到正确分支之前所判断条件体的数量。最简单的优化方法是将最常见的条件体放在首位。考虑下面的例子:

 

if (value < 5) {
  //do something
} else if (value > 5 && value < 10) {
  //do something
} else {
  //do something
}

    This code is optimal only if value is most frequently less than 5. If value is typically greater than or equal to 10, then two conditions must be evaluated each time before the correct path is taken, ultimately increasing the average amount of time spent in this statement. Conditions in an if-else should always be ordered from most likely to least likely to ensure the fastest possible execution time.

    这段代码只有当value值经常小于5时才是最优的。如果value经常大于等于10,那么在进入正确分支之前,必须两次运算条件体,导致表达式的平均时间提高。if-else中的条件体应当总是按照从最大概率到最小概率的顺序排列,以保证理论运行速度最快。

 

    Another approach to minimizing condition evaluations is to organize the if-else into a series of nested if-else statements. Using a single, large if-else typically leads to slower overall execution time as each additional condition is evaluated. For example:

    另外一种减少条件判断数量的方法是将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;
}

    With this if-else statement, the maximum number of conditions to evaluate is 10. This slows down the average execution time if you assume that the possible values for value are evenly distributed between 0 and 10. To minimize the number of conditions to evaluate, the code can be rewritten into a series of nested if-else statements, such as:

    在这个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;
    }
  }
}

    The rewritten if-else statement has a maximum number of four condition evaluations each time through. This is achieved by applying a binary-search-like approach, splitting the possible values into a series of ranges to check and then drilling down further in that section. The average amount of time it takes to execute this code is roughly half of the time it takes to execute the previous if-else statement when the values are evenly distributed between 0 and 10. This approach is best when there are ranges of values for which to test (as opposed to discrete values, in which case a switch statement is typically more appropriate).

    在重写的if-else表达式中,每次抵达正确分支时最多通过四个条件判断。它使用二分搜索法将值域分成了一系列区间,然后逐步缩小范围。当数值范围分布在0到10时,此代码的平均运行时间大约是前面那个版本的一半。此方法适用于需要测试大量数值的情况(相对离散值来说switch更合适)。

 

Lookup Tables  查表法

 

    Sometimes the best approach to conditionals is to avoid using if-else and switch altogether. When there are a large number of discrete values for which to test, both if-else and switch are significantly slower than using a lookup table. Lookup tables can be created using arrays or regular objects in JavaScript, and accessing data from a lookup table is much faster than using if-else or switch, especially when the number of conditions is large (see Figure 4-1).

    有些情况下要避免使用if-else或switch。当有大量离散值需要测试时,if-else和switch都比使用查表法要慢得多。在JavaScript中查表法可使用数组或者普通对象实现,查表法访问数据比if-else或者switch更快,特别当条件体的数目很大时(如图4-1)。

Figure 4-1. Array item lookup versus using if-else or switch in Internet Explorer 7

图4-1  Internet Explorer 7中数组查询与if-else或switch的比较

 

    Lookup tables are not only very fast in comparison to if-else and switch, but they also help to make code more readable when there are a large number of discrete values for which to test. For example, switch statements start to get unwieldy when large, such as:

    与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;
}

    The amount of space that this switch statement occupies in code is probably not proportional to its importance. The entire structure can be replaced by using an array as a lookup table:

    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];

    When using a lookup table, you have completely eliminated all condition evaluations. The operation becomes either an array item lookup or an object member lookup. This is a major advantage for lookup tables: since there are no conditions to evaluate, there is little or no additional overhead as the number of possible values increases.

    当使用查表法时,必须完全消除所有条件判断。操作转换成一个数组项查询或者一个对象成员查询。使用查表法的一个主要优点是:由于没有条件判断,当候选值数量增加时,很少,甚至没有增加额外的性能开销。

 

    Lookup tables are most useful when there is logical mapping between a single key and a single value (as in the previous example). A switch statement is more appropriate when each key requires a unique action or set of actions to take place.

    查表法最常用于一个键和一个值形成逻辑映射的领域(如前面的例子)。一个switch表达式更适合于每个键需要一个独特的动作,或者一系列动作的场合。

 

Recursion  递归

 

    Complex algorithms are typically made easier by using recursion. In fact, there are some traditional algorithms that presume recursion as the implementation, such as a function to return factorials:

    复杂算法通常比较容易使用递归实现。事实上,有些传统算法正是以递归实现的,诸如阶乘函数:

 

function factorial(n){
  if (n == 0){
    return 1;
  } else {
    return n * factorial(n-1);
  }
}

    The problem with recursive functions is that an ill-defined or missing terminal condition can lead to long execution times that freeze the user interface. Further, recursive functions are more likely to run into browser call stack size limits.

    递归函数的问题是,一个错误定义,或者缺少终结条件可导致长时间运行,冻结用户界面。此外,递归函数还会遇到浏览器调用栈大小的限制。

 

Call Stack Limits  调用栈限制

 

    The amount of recursion supported by JavaScript engines varies and is directly related to the size of the JavaScript call stack. With the exception of Internet Explorer, for which the call stack is related to available system memory, all other browsers have static call stack limits. The call stack size for the most recent browser versions is relatively high compared to older browsers (Safari 2, for instance, had a call stack size of 100). Figure 4-2 shows call stack sizes over the major browsers.

    JavaScript引擎所支持的递归数量与JavaScript调用栈大小直接相关。只有Internet Explorer例外,它的调用栈与可用系统内存相关,其他浏览器有固定的调用栈限制。大多数现代浏览器的调用栈尺寸比老式浏览器要大(例如Safari 2调用栈尺寸是100)。图4-2显示出主流浏览器的调用栈大小。

Figure 4-2. JavaScript call stack size in browsers

图4-2  浏览器的JavaScript调用栈尺寸

 

    When you exceed the maximum call stack size by introducing too much recursion, the browser will error out with one of the following messages:

    当你使用了太多的递归,超过最大调用栈尺寸时,浏览器会出错并弹出以下信息:

 

• Internet Explorer: “Stack overflow at line x”

• Firefox: “Too much recursion”

• Safari: “Maximum call stack size exceeded”

• Opera: “Abort (control stack overflow)”

 

    Chrome is the only browser that doesn't display a message to the user when the call stack size has been exceeded.

    Chrome是唯一不显示调用栈溢出错误的浏览器。

 

    Perhaps the most interesting part of stack overflow errors is that they are actual JavaScript errors in some browsers, and can therefore be trapped using a try-catch statement. The exception type varies based on the browser being used. In Firefox, it's an InternalError; in Safari and Chrome, it's a RangeError; and Internet Explorer throws a generic Error type. (Opera doesn't throw an error; it just stops the JavaScript engine.) This makes it possible to handle such errors right from JavaScript:

    关于调用栈溢出错误,最令人感兴趣的部分大概是:在某些浏览器中,他们的确是JavaScript错误,可以用一个try-catch表达式捕获。异常类型因浏览器而不同。在Firefox中,它是一个InternalError;在Safari和Chrome中,它是一个RangeError;在Internet Explorer中抛出一个一般性的Error类型。(Opera不抛出错误;它终止JavaScript引擎)。这使得我们能够在JavaScript中正确处理这些错误:

 

try {
  recurse();
} catch (ex){
  alert("Too much recursion!");
}

    If left unhandled, these errors bubble up as any other error would (in Firefox, it ends up in the Firebug and error consoles; in Safari/Chrome it shows up in the JavaScript console), except in Internet Explorer. IE will not only display a JavaScript error, but will also display a dialog box that looks just like an alert with the stack overflow message.

    如果不管它,那么这些错误将像其他错误一样冒泡上传(在Firefox中,它结束于Firebug和错误终端;在Safari/Chrome中它显示在JavaScript终端上),只有Internet Explorer例外。IE不会显示一个JavaScript错误,但是会弹出一个提示堆栈溢出信息的对话框。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值