[翻譯]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錯誤,但是會彈出一個提示堆棧溢出信息的對話框。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章