# 迴圈

迴圈(loop)是"重覆作某個事"的語句結構，用比較詳細的解釋，則是"在一個滿足的條件下，重覆作某件事幾次"這樣的語句。

迴圈語句經常會搭配陣列與物件之類的集合性資料結構使用，重覆地或循環地取出某些滿足條件的值，或是重覆性的更動其中某些值。

## for語句

for語句需要三個表達式作為參數，它們各自有其功用，分別以分號(;)作為區分。for迴圈的基本語法如下:

```javascript
for ([initialExpression]; [condition]; [incrementExpression])
  statement
```

* initialExpression: 初始(開始時)情況的表達式
* condition: 判斷情況或測試條件，代表可執行for中包含執行動作的滿足條件
* incrementExpression: 更新表達式，每次for迴圈中的執行動作語句完成即會執行一次這個表達式

一個簡單的範例如下:

```javascript
for (let count = 0 ; count < 10 ; count++){
    console.log(count)
}
```

整個程式碼的解說是:

* 在迴圈一開始時，將`count`變數指定為數字`0`
* 定義當`count < 10`時，才能滿足執行`for`中區塊語句的條件。也就是只有在`count < 10`時才能執行for中區塊語句。
* 每次當執行`for`中區塊的語句執行後，即會執行`count++`

### 可自訂的表達式們

`for`語句中共有三個表達式，分別代表迴圈進行時的設定與功用，這三個表達式的運用方式就可以視不同的應用情況而調整。所有的三個表達式都是可自訂的，也是可有可無。下面這個看起來怪異的`for`語句是一個無窮迴圈，執行這個語句會讓你的瀏覽器當掉:

```javascript
//錯誤示範
for (;;){
    console.log('bahbahbah')
}
```

> 註: 用`for(;;)`這樣的語法，基本上失去`for`語句原本的意義，完全不建議使用。
>
> 注意: 迴圈是一個破壞性相當高的語句，如果不小心很容易造成整個程式錯誤或當掉。

第一個表達式是用作初始值的定義。它是可以設定多個定義值的，每個定義值之間使用逗號(,)作為分隔，定義值可使用的範圍只在迴圈內部的語句中:

```javascript
for (let count = 0, total = 10 ; count < 10 ; count++){
    console.log(count, total)
}
```

> 註: 初始值同樣也可以定義在迴圈之外，但它的作用範圍會擴大到迴圈之外。

第二個表達式是用於判斷情況(condition)，也就是每次執行迴圈中區塊的語句前，都會來測試(檢查)一下，是不是滿足其中的條件，如果滿足的話(布林`true`值)，就執行迴圈中區塊的語句，不滿足的話(布林`false`值)，就跳出迴圈語句或略過它。這個表達式就是會造成無止盡的或重覆執行次數不正確的原兇，所以它是這三個表達式中要特別注意的一個。

由於它是一個標準的判斷情況(condition)表達式，如果有多個判斷情況的時候，要使用邏輯與(&&)和邏輯或(||)來連接，邏輯或(||)因為結果會是`true`的情況較為容易，所以要更加小心:

```javascript
for (let count = 0, total = 10 ; count < 10 && total < 20 ; count++){
    console.log(count, total)
}
```

第三個表達式是用於更新的，常被稱為遞增表達式(incrementExpression)。不過它也不只用在遞增(increment)，應該說用在"更動(update)"或許會比較恰當，每次一執行完迴圈內部語句就會執行一次的表達式。它的作用相當於在迴圈內的區塊中的最後面加上這個語句，例如下面這個for迴圈和最上面一開始的範例的結果是一樣的:

```javascript
for (let count = 0 ; count < 10 ;){
    console.log(count)
    count++
}
```

多個表達式也是可以的，同樣也是用逗點(,)分隔不同的表達式:

```javascript
for (let count = 0, total = 30 ; count < 10 && total > 20 ; count++, total--){
    console.log(count, total)
}
```

這裡有個小小問題，是有關於遞增運算符(++)與遞減運算符(--)，它們運算元的位置差異，放在運算元的前面和後面差異是什麼？例如以下的範例:

```javascript
let x = 1
let y = 1

console.log(x++) //1
console.log(x) //2

console.log(++y) //2
console.log(y) //2
```

所以放在運算元(值)前面(先)的遞增(++)或遞減(--)符號，就會先把值作加1或減1，也就會直接變動值。放後面的就是值的變動要在下一次再看得到。

> 口訣: 遞增減運算符(++/--) "錢仙"=(放)前(面)先(變動)

#### 小結

實際上這三個表達式並沒有限定只能用哪些表達式，只要是合法的表達式都可以，只不過第二個表達式會作判斷情況，也就是轉換為布林值，這一點要注意的。有些程式設計師喜歡用這個特性寫出感覺很高超的語法，只是讓別人更難看懂在寫什麼而已，我完全不認同這種寫法，如何提供高閱讀性的程式碼才是最好的寫法。

至於每個表達式執行的情況，第一個表達式(初始化值)只會在讀取到`for`語句時執行一次，之後不會再執行。第二個表達式(判斷情況)會在讀取到`for`語句時執行一次，之後每次重覆開始時都會執行一次。第三個表達式會在有滿足條件下，執行到`for`語句區塊內部的語句的最後才執行。

### 多重迴圈

所謂的多重迴圈是巢狀的迴圈語句結構，也就是在迴圈的區塊語句中，再加上另一個內部的迴圈語句。典型的範例就是九九乘法表:

```javascript
for (let i = 1 ; i < 10 ; i++){
  for (let j = 1 ; j < 10 ; j++){
    console.log(i + ' x ' + j + ' = ', i * j )
  }
}
```

> 註: 下面的`while`語句與`do...while`語句也可以寫為多重迴圈(巢狀迴圈)，類似上面的程式碼，就不再多重覆說明了。

## while語句

相較於需要很確定重覆次數(重覆次數為可被計算的)的`for`迴圈，`while`迴圈語句可以說它是`for`迴圈語句的簡單版本，你可以根據不同的使用情況選擇要使用哪一種。它只需要一個判斷情況(condition)的表達式即可，它的基本語法結構如下:

```javascript
while (condition)
  statement
```

一個簡單的範例如下，這個範例與之前的for迴圈的範例功能是相等的:

```javascript
let count = 0

while (count < 10) {
    console.log(count)
    count++
}
```

while語句沒什麼特別要說明的，它把更新用表達式的部份交給程式設計師自行處理，在while語句中的區塊語句中再加上。使用時仍要避免出現無窮迴圈或重覆次數不正確的情況。

## do...while語句

`do...while`語句是`while`語句的另一種變形的版本，差異只在於"它會先執行一次區塊中的語句，再進行判斷情況"，也就是會"保証至少執行一次其中的語句"，它的基本語法結構如下:

```javascript
do
  statement
while (condition)
```

正常的使用情況與while語句沒什麼差異，只是位置上下顛倒再加個`do`而已:

```javascript
let count = 0

do {
  console.log(count)
  count++
}
while (count < 10)
```

實際上`while`語句如果要相等於`do...while`的語句，應該是如下面這樣，也就是while語句前要先執行一次`do...while`中的語句才能算相等:

```javascript
do_statment
while (condition)
  do_statment
```

因為`do...while`語句具有先執行語句再檢查，也就是保証會執行一次的特性，要視應用情況來使用，以簡單的實際例子來說明`while`與`do...while`語句的應用情況:

* `while`: 當有一群人要進入電影院，需要對每個入場者收取門票與檢查，然後才能入場
* `do...while`: 當有幾組參賽者參加歌唱比賽，需要先表演後再對每個參賽者評分

## for...in語句

`for...in`語句主要是搭配物件類型使用的，不要使用在陣列資料類型。它是用於迭代物件的可列舉的屬性值(enumerable properties)，而且是任意順序的。因為陣列資料在進行迭代時，順序是很重要的，所以陣列資料類型不會用這個語句，而且陣列資料類型本身就有太多好用的迭代方法。

另外，`for`廻圈語句完全可以取代`for...in`語句，而且`for`廻圈語句可以保証順序，所以這個語句算是不一定要使用的，在這裡說明只是單純的比較其他的語句而已。

> 註: `for...in`語句是任意順序的迴圈語句，使用時通常是用來檢測用的語句
>
> 註: 對於複雜的物件資料類型，單純使用JavaScript中的語言特性是常常有所不足，建議是使用其他的函式庫中的API，例如jQuery、Lodash或underscore.js。

## for...of語句

`for...of`語句是新的ES6語句，可以用於可迭代物件上，取出其中的值，可迭代物件包含陣列、字串、Map物件、Set物件等等。簡單的範例如下:

```javascript
//用於陣列
let aArray = [1, 2, 3]

for (let value of aArray) {
  console.log(value)
}

//用於字串
let aString = "abcd";

for (let value of aString) {
  console.log(value);
}
```

### break與continue

`break`(中斷)與`continue`(繼續)是用於迴圈區塊中語句的控制，在`switch`語句中也有看過`break`這個關鍵字的用法，是一個跳出`switch`語句的語法。

`break`是"中斷整個迴圈語句"的意思，可以中斷整個迴圈，也就是"跳出"迴圈區塊的語句，如果是在巢狀迴圈時是跳出最近的一層。`break`通常會用在迴圈語句中的`if`判斷語句裡，例如下面這個例子:

```javascript
let count = 0

while (count < 10) {
    console.log(count)
    //count的值為6時將會跳出廻圈
    if(count === 6) break
    count++
}
```

`continue`是"繼續下一次迴圈語句"的意思，它會忽略在`continue`之下的語句，直接跳到下一次的重覆執行語句的最開頭。因為`continue`有"隱含的`goto`到迴圈的最開頭程式碼"，它算是一個在JavaScript語言中的壞特性，它會讓你的判斷情況(condition)整個無效化，也可以破壞整體的迴圈語句執行結構，容易被濫用出現不被預期的結果，結論是能不用就不要使用。一個簡單的範例如下:

```javascript
let count = 0
let a = 0

while (count < 10) {
    console.log('count = ', count)
    console.log('a = ', a)

    count++
    //count的值大於為6時將會不再遞增a變數的值
    if(count > 6) continue
    a++
}
```

> 註: 雖然JavaScript語言中並沒有`goto`語法，但迴圈語句中的`continue`搭配`labeled語句`，相當於其他程式語言中的`goto`語法。`goto`語法幾乎在所有的程式語言中，都是惡名昭彰的一種語法結構。

## 風格指引

* (Airbnb 18.3) 在控制語句的圓括號開頭(()前放一個空格(if, while等等)。在函式呼叫與定義的函式名稱與傳入參數之間就不需要空格。eslint: keyword-spacing jscs: requireSpaceAfterKeywords
* (Airbnb 7.3) 絕對不要在非函式區塊中宣告一個函式(if, while等等)。改用指定一個函式給一個變數。瀏覽器可以允許你可以這樣作，但是壞消息是，不同瀏覽器可能會轉譯為不同的結果。eslint: no-loop-func
* (Google) for-in迴圈經常會錯誤地被使用在陣列上。當使用陣列時，總是使用一般的for迴圈。
* 在迴圈中區塊語句裡，定義變數/常數是個壞習慣，應該是要定義在迴圈外面或for迴圈的第一個表達式中。

```javascript
//壞的寫法
for (let i=0; i<10; i++){
  let j = 0
  console.log(j)
  j++
}

//好的寫法
for (let i=0, let j = 0; i<10; i++){
  console.log(j)
  j++
}
```

* 用於判斷情況的運算，應該要儘可能避免，先作完運算在迴圈外部或或for迴圈的第一個表達式中，效率可以比較好些。

```javascript
//較差的寫法
for (let i=0 ; i < aArray.length ; i++) {
  //statment
}

//較好的寫法
for (let i=0, len = aArray.length ; i < len; i++) {
  //statment
}

//另一種寫法，這種寫法腦袋要很清楚才看得懂
for (let i = aArray.length; i--;){
  //statment
}

//較差的寫法
let i = 0
while (i < aArray.length) {
  //statment
  i++
}

//較好的寫法
let i = 0
let len = aArray.length

while ( i < len) {
  //statment
  i++
}
```

> 註: 上面有一說法是在陣列資料類型的迴圈中的第三表達式(更新用表達式)中，使用i--會比i++效率更佳。實際上在現在的瀏覽器上這兩種的執行速度是根本一樣，沒什麼差異。

## 結語

## 家庭作業


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://eddy-chang.gitbook.io/javascript-start-from-es6/part3/loop.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
