Learning Objectives
- 用 if、else if、else 依條件選擇要執行的程式,並判斷哪些值會被當成 false
- 用 switch 依一個值選擇 case,說明 break 的作用與忘了 break 時的穿透
- 用三元運算子寫出簡短的二選一
- 用 for、while、do…while 重複執行程式,並說明三者的差別
- 用 break、continue 與 label 控制迴圈提早結束或跳過一圈
為什麼需要流程控制
到目前為止,程式都是由上往下一行一行執行。真正的程式常常需要:
- 選擇:分數 90 分以上顯示 A,其他顯示 B;使用者沒登入就顯示登入按鈕
- 重複:把 50 位同學的成績一一加總;每秒更新一次時鐘
這兩件事分別由選擇敘述(if、switch)與迴圈(for、while、do…while)負責。
它們都會搭配區塊(block)使用:一對大括號 { } 把好幾個敘述包成一組,條件成立時整組一起執行。
if (條件) {
敘述 1;
敘述 2;
}
區塊裡只有一個敘述時,大括號可以省略,但很容易在之後加一行時出錯,建議一律寫上大括號。
if…else if…else:第一個成立的條件才執行
grade 被呼叫兩次。第一次 score 是 85:注意第 4 行的條件不成立,直接跳到第 6 行檢查下一個條件;第 6 行成立之後,後面的 else if、else 都不再檢查。
條件不一定是 true 或 false
if (…) 的括號裡可以放任何值,JavaScript 會先把它轉成布林值。只有下面六種值會被當成 false,叫做 falsy:
| 值 | 說明 |
|---|---|
false |
布林值 false 本身 |
0 |
數字 0(-0 也是) |
'' |
空字串 |
null |
空值 |
undefined |
未定義 |
NaN |
不是數字 |
其他的值都是 truthy,包括 '0'、'false'(不是空字串)、[](空陣列)、{}(空物件)。
所以 if (name) 常用來檢查「有沒有填名字」:空字串會被當成 false。
三元運算子(條件 ? 成立時的值 : 不成立時的值)是 if…else 的簡短寫法,適合「二選一得到一個值」的情況:
const result = score >= 60 ? '及格' : '不及格';
邏輯比較複雜時還是用 if…else,三元運算子一層一層套下去會很難讀。
哪些值是 falsy
用三元運算子檢查陣列裡的八個值。先猜猜看:'0'、[] 和 'false' 會被當成 true 還是 false?
switch:依一個值選擇 case
當你要拿同一個值和好幾個可能的值比較時,用 switch 比一長串 else if 清楚:
switch (運算式) {
case 值1:
// 運算式 === 值1 時執行
break;
case 值2:
// 運算式 === 值2 時執行
break;
default:
// 都不符合時執行(可以省略)
}
三個規則:
- 比對用的是嚴格相等
===:switch ('1')不會進入case 1: - 找到符合的 case 後,會一直往下執行,直到遇到
break或 switch 結束。忘了寫break時,後面的 case 也會執行,這叫穿透(fall-through) - 都不符合時執行
default
穿透有時是刻意的:幾個 case 要做同一件事時,可以把 case 疊在一起。
switch (month) {
case 12:
case 1:
case 2:
season = '冬天';
break;
// …
}
switch、break 與穿透
第一個 switch 每個 case 都有 break。第二個 switch 的 case 2 沒有 break:看第 22 行執行完之後,程式往哪一行走?
想一想:選擇敘述
下面的程式會印出什麼?
const input = '0';
if (input) {
console.log('有輸入');
} else {
console.log('沒有輸入');
}
下面的程式會印出什麼?
const level = 1;
switch (level) {
case 1:
console.log('初級');
case 2:
console.log('中級');
break;
case 3:
console.log('高級');
}
下面的程式會印出什麼?
const choice = '2';
switch (choice) {
case 2:
console.log('選了二');
break;
default:
console.log('不認得');
}
三種迴圈
| 迴圈 | 寫法 | 適合 |
|---|---|---|
for |
for (初始; 條件; 遞增) { … } |
知道要跑幾圈,例如 1 到 10、走訪陣列的索引 |
while |
while (條件) { … } |
不知道要跑幾圈,只知道什麼時候該停 |
do…while |
do { … } while (條件); |
至少要先執行一次,再決定要不要繼續 |
for 的執行順序:
- 執行初始(只有一次),例如
let i = 1 - 檢查條件,成立就執行本體;不成立就結束迴圈
- 本體執行完,執行遞增(例如
i++),回到第 2 步
用 let 宣告的迴圈變數只活在迴圈裡,迴圈結束後就不見了。
要依序拿出陣列的每一個元素時,for…of 更簡潔,不需要自己管索引:
for (const color of ['red', 'green', 'blue']) {
console.log(color);
}
物件要用 for…in,兩者的差別在下一課〈物件〉說明。
寫迴圈時一定要確認條件總有一天會不成立,否則就是停不下來的無窮迴圈,網頁會卡住。
for 迴圈:1 加到 5
看第 3 行出現了好幾次:每一次都是「遞增之後再檢查條件」。注意 i 變成 6 的那一步,條件不成立,迴圈結束,第 6 行時 i 已經從變數區消失了。
while 與 do…while 的差別
前兩個迴圈的條件一開始就不成立(10 < 3 是 false)。while 一次都不執行,do…while 先執行了一次。第三個迴圈事先不知道要跑幾圈,只知道「超過 100 就停」,所以用 while。
break 與 continue
| 敘述 | 作用 |
|---|---|
break |
立刻結束整個迴圈(或 switch),跳到迴圈後面的程式 |
continue |
跳過這一圈剩下的部分,直接進入下一圈:for 會先執行遞增,while 會回去檢查條件 |
巢狀迴圈裡,break 和 continue 只影響最內層的迴圈。要一次跳出外層,可以替外層迴圈加上 label(標籤),
再寫 break 標籤名稱:
outer:
for (…) {
for (…) {
if (找到了) break outer; // 兩層一起結束
}
}
label 在實際的程式裡不常用;多數時候把巢狀迴圈放進函式,找到時直接 return,會更容易讀。
continue 跳過一圈,break 結束迴圈
3 和 6 是 3 的倍數,遇到 continue 跳過不印;i 變成 8 時遇到 break,迴圈直接結束,9 和 10 都不會執行到。
巢狀迴圈與 label
上半段是小型乘法表:外層 i 每跑一圈,內層 j 都從 1 跑到 3。下半段找乘積為 4 的組合,找到時用 break outer 一次跳出兩層迴圈。
想一想:迴圈
下面的程式會印出什麼?
const out = [];
for (let i = 1; i <= 5; i++) {
if (i === 2) continue;
if (i === 4) break;
out.push(i);
}
console.log(out.join(','));
下面的程式會印出什麼?
let count = 0;
do {
count++;
} while (count > 5);
console.log(count);
下面的程式會印出什麼?
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) break;
console.log(i, j);
}
}
實作:FizzBuzz
用 for 迴圈印出 1 到 15,但是:3 的倍數印 Fizz、5 的倍數印 Buzz、同時是 3 和 5 的倍數印 FizzBuzz,其他數字照常印出。想一想:if…else if 的順序要怎麼排?
重點精華回顧
if…else if…else由上往下檢查,第一個成立的條件才執行,後面的都跳過。false、0、''、null、undefined、NaN是 falsy,其他值(包括'0'、[])都是 truthy。- 三元運算子
條件 ? A : B適合簡短的二選一。 switch用===比對 case,執行到break才離開;忘了break會穿透到下一個 case;都不符合時執行default。for適合知道圈數的重複;while先檢查再執行,條件成立就一直重複;do…while至少執行一次。break結束整個迴圈,continue跳過這一圈;巢狀迴圈要一次跳出外層時可以用 label。