iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 9
8
Modern Web

重新認識 JavaScript系列 第 9

重新認識 JavaScript: Day 09 流程判斷與迴圈

本系列文章已重新編修,並在加入部分 ES6 新篇章後集結成書,有興趣的朋友可至天瓏書局選購,感謝大家支持。

購書連結 https://www.tenlong.com.tw/products/9789864344130

讓我們再次重新認識 JavaScript!


在上一篇介紹過 JavaScript 的 truefalse 奧妙之處 (?) 後,今天就來繼續談談流程控制的部分。

在開始今天的內容之前,我們先來看個在網路上流傳多年的老笑話:

工程師某日在下班前接到老婆的電話:
老婆說:「下班買十顆包子回家,如果看到賣西瓜的,就買一個。」
晚上工程師回到家,老婆看了就問:「怎麼只買了一個包子?」
工程師:「因為看到賣西瓜的。」

如果你不懂工程師笑點的話,那麼恭喜你有個正常人的腦袋。 XD

JavaScript 用來控制流程的「條件語法」指的是,當指定的條件為 true 時,就會執行後續所指定的指令。 而 JavaScript 的條件語法有兩種︰ if...elseswitch


條件語法 (1): if...else

就如同字面上一樣,「如果」怎樣怎樣,就做某件事,「否則」做另一件事,語法像這樣︰

if ( 條件式 ){
  // 某條件成立會執行這個 { } 區塊中的內容
  // ...
}
else{
  // 否則執行 else 這個 { } 區塊中的內容
  // else 可以有也可以沒有
}

上面的「條件式」可以是 truefalse 的表達式。 還記得前一篇說的嗎?

「JavaScript 所有資料都可以透過 ToBoolean 轉換成 truefalse

轉換的規則請看前一篇 Day 08 運算式與運算子 [3]: Boolean 的真假判斷

除了 if...else 之外,你也可以使用 else if 來新增條件:

if ( 條件式 ){
  // 某條件成立會執行這個 { } 區塊中的內容
  // ...
}
else if( 條件式2 ){
  // ...
}
else if( 條件式3 ){
  // ...
}
else if( 條件式4 ){
  // ...
}
else{
  // 若上面所有條件判斷都是 false ,才會執行 else 這個 { } 區塊中的內容

}

else if 基本上沒有數量的限制,但為了程式的可讀性與維護性,還是盡量減少 if...else 的條件數量喔。

所以回到前面笑話,寫成程式就會寫成這樣:

// 下班買十顆包子回家,如果看到賣西瓜的,就買一個。

var buy = "十顆包子";

if( 看到賣西瓜的 ) {
  buy = "一個包子";
}

然後回家你就準備跪算盤了。


條件語法 (2): switch

這裏要講的 switch 不是任天堂那個 switch,而是流程判斷的另一個語法。

前面我們有提到,當你要做的條件判斷很多的時候,可能會寫成這樣:

if ( 條件式 ){
  // 某條件成立會執行這個 { } 區塊中的內容
  // ...
}
else if( 條件式2 ){
  // ...
}
else if( 條件式3 ){
  // ...
}
else if( 條件式4 ){
  // ...
}
else{
  // 若上面所有條件判斷都是 false ,才會執行 else 這個 { } 區塊中的內容
  // ...
}

像這樣,假設 1~3 月春天,4~6 月夏天,7-9 月秋天,10-12 月冬天,
而我們要從月份來判斷季節,用 if...else 可以這樣寫:

var month = 12;

// 註:Math.ceil(n) 代表將 n 數值無條件進位

if( Math.ceil( month/3 ) === 1 ){
  console.log('春天');
}
else if( Math.ceil( month/3 ) === 2 ){
  console.log('夏天');
}
else if( Math.ceil( month/3 ) === 3 ){
  console.log('秋天');
}
else if( Math.ceil( month/3 ) === 4 ){
  console.log('冬天');
}
else{
  console.log('月份錯誤');
}

像這樣,如果不幸是冬天的情況下,你的 Math.ceil( month/3 ) 至少要做四次,且程式碼太長也不好維護。

如果改用 switch 的語法,可以這樣寫:

var month = 12;

switch ( Math.ceil(month/3) ){
  case 1:
    console.log('春天');
    break;
  case 2:
    console.log('夏天');
    break;
  case 3:
    console.log('秋天');
    break;
  case 4:
    console.log('冬天');
    break;
  default:
    console.log('月份錯誤');
    break;
}

程式碼看起來是不是簡潔很多呢?

以上面的範例來說, switch 括號內的語法可能是運算式或是某個變數、值,像上面就是 Math.ceil(month/3)

接著會進入 case 來判斷,若switch 括號內的結果剛好是 case 後面的「值」,則會執行 case 區塊內的指令。

default 的區塊就是當上面所有 case 都不成立的時候會執行。

需要注意的是,case 區塊並不像 if...else 用大括號 { } 來切分區塊的,而是用 break 指令。
如果沒有加上 breakmonth = 1 的情況下會像這樣:

var month = 1;

switch ( Math.ceil(month/3) ){
  case 1:
    console.log('春天');
  case 2:
    console.log('夏天');
  case 3:
    console.log('秋天');
  case 4:
    console.log('冬天');
  default:
    console.log('月份錯誤');
}

https://ithelp.ithome.com.tw/upload/images/20171212/20065504Lz24e8V5eZ.png
像這樣,程式就會一路綠燈開到底了,除非你中間插入 break 語法。

但「適當地」利用 break 語法的話,你也可以這樣做:

var month = 1;

switch ( month ){
  case 1:
  case 2:
  case 3:
    console.log('春天');
    break;
  case 4:
  case 5:
  case 6:
    console.log('夏天');
    break;
  case 7:
  case 8:
  case 9:
    console.log('秋天');
    break;
  case 10:
  case 11:
  case 12:
    console.log('冬天');
    break;
  default:
    console.log('月份錯誤');
}

所以除非你很清楚你要的結果,否則強烈建議 case 區塊結束前一定要加入 break 來終止。


三元運算子: (條件式) ? A : B

講完 if...elseswitch 之後,我才想起前面的「運算子」系列我遺漏了一個很有趣的東西:「三元運算子」。

三元運算子,又稱「條件運算子」,分別以 「問號」 ? 以及「冒號」: 所組合而成:

(條件) ? [數值1] : [數值2];

如果 「條件」 為 true,此時回傳 數值1, 否則回傳 數值2。

實際使用的方式:

var status = (age >= 18) ? '成人' : '小孩';

像這樣會先判斷變數 age 有沒有大於或等於 18

如果有的話, status 的值會是 '成人',否則就是 '小孩'


迴圈

接下來看到迴圈的部分。

所謂「迴圈」指的是,想要重複做某件事,而數值會依次數有「遞增」或「遞減」的變化來完成退出的條件。

什麼意思? 舉個例子。

假設我們要用 console.log()1 印到 10,在沒有使用迴圈的情況下:

console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
console.log(6);
console.log(7);
console.log(8);
console.log(9);
console.log(10);

同樣的東西你得寫十次,看起來很蠢對吧?

https://ithelp.ithome.com.tw/upload/images/20171212/20065504DwpetlpsNd.jpg

在 JavaScript 裡面,迴圈的常見語法有 for 以及 while 兩種。
那麼我們就來看看改用「迴圈」之後的寫法可以多簡便。


for 迴圈

一個簡單的 for 迴圈語法會是這樣:

var i;

for (i = 0; i < 10; i++) {
  // 做某件事
}

小括號 ( ) 的內容可以分成三個部分:

https://ithelp.ithome.com.tw/upload/images/20171212/20065504mtLElpg45U.png

畫紅線的是「初始值」,用來初始化 for 迴圈中的計數器。

你可能會看過有人這樣寫:

for (var i = 0; i < 10; i++) {
  // 做某件事
}

雖然在這裡可以用 var 來宣告變數,但要小心,這裡的變數並不是專屬 for 迴圈內的變數,變數 i 的有效範圍其實跟 for 迴圈是相同的

綠線的部分是「執行迴圈的條件」,指的是當滿足這個條件 (結果為 true) 的時候,就會進入大括號 { } 的區塊,然後執行內部程式。

藍線的部分是,在每一次執行完大括號 { } 區塊的程式碼之後,會執行這段程式碼。

換言之,假設我們要用 console.log()1 印到 10,用 for 的寫法就是:

for (var i = 1; i <= 10; i++) {
  console.log(i);
}

while 迴圈

然後我們看看 while 迴圈。
把剛剛從 1 印到 10,改用 while 的寫法就是:

var i = 1;

while ( i <= 10 ){
  console.log( i );
  i++;
}

可以看到, while 迴圈的語法就顯得單純一些。

括號 () 內代表的是「執行迴圈的條件」,指的是當滿足這個條件 (結果為 true) 的時候,就會進入大括號 { } 的區塊,然後執行內部程式。

https://ithelp.ithome.com.tw/upload/images/20171212/200655047mXYCHc6oE.png

for 相比,兩者在執行時的原理其實大同小異。

然而需要注意的是,不管 forwhile 迴圈,「結束的條件」是很重要的

在執行迴圈的時候,若是「執行條件」沒有設定好,很容易變成「無窮迴圈」在裡面無限空轉,像:

// i 不管怎麼樣都會大於等於 0

for (var i = 0; i >= 0; i++) {
  console.log(i);
}

或是

var i = 1;

// 結束迴圈時, i 的值仍然是 1
while ( i <= 10 ){
  console.log( i );
}

因為條件不管怎麼樣都會成立,寫成這樣的話你就跳不出迴圈啦!


breakcontinue

剛才我們看過了 forwhile 基本語法,
那麼假如我們想要跳過其中幾次,或是想要提早離開迴圈,有什麼方式呢?

這時候你會需要 breakcontinue

兩者的功能差別:

  • break 會直接跳離迴圈
  • continue 會跳過一次,然後繼續下一次迴圈。

所以說,假設我們想要印出 1 ~ 10 的所有數字,但跳過 3 的倍數
:

for (var i = 1; i <= 10; i++) {

  // i 能被 3 整除表示 i 是 3 的倍數,遇到 continue 就會跳過這次
  if( i % 3 === 0){
    continue;
  }

  console.log(i);
}

有個陣列,假設裡面包含一堆 0 與若干其他數字,而我們不知道裡面內容。
我們想要找出不是「0」的「第一個數字」是哪一個:

// 假設陣列是這樣包含一堆 0 與若干其他數字
var arr = [0,0,0,0,7,0,9,0,4,8,0];

for(var i = 0; i < arr.length; i++ ) {

  if( arr[i] !== 0 ){
    // 找到那個不是 0 的數字,印出後退出迴圈
    console.log( arr[i] );
    break;
  }
}


forwhile 兩者的差異點

在大多數情況之下,forwhile 迴圈兩者能做的事情是一樣的。

那麼有沒有什麼事情是非 for 不可或非 while 不可呢?

在今天文章的最後,我簡單做個區別:

  • for 迴圈的使用情境,大多是用在迴圈執行次數「明確」的狀態
  • while 迴圈則剛好相反,當迴圈執行次數「不確定」的時候更適合。

什麼意思? 迴圈的特性都是,「只要指定條件是 true」就可以進入迴圈區塊對吧?

因為 for 迴圈同時包含了「初始值」、「條件」以及「結束迴圈的更新」三個部分,使得它的執行次數你可以一眼就看出來; 而 while 迴圈只包含「條件」的部分。

用一個我很喜歡的例子來說,假設我們要把「大樂透」的電腦選號規則程式化:「從 1 ~ 49 中選 6 個不重複號碼」

while 迴圈的話,你可以這樣寫:

var lottery = [];
var n;

// 直到陣列 lottery 選滿 6 球
while(lottery.length < 6) {

  // 取一隨機 1 ~ 49 數字
  n = Math.floor( Math.random() * 49 ) + 1;

  // 如果選出來的 n 不存在,就放入陣列
  if( lottery.indexOf( n ) === -1 ){
    lottery.push( n );
  }
}

思考一下,在這樣的規則下,要用 for 迴圈來寫,可以怎麼寫?
就留待各位自行想像囉 XD


那麼以上就是今天分享的內容,謝謝收看。


上一篇
重新認識 JavaScript: Day 08 Boolean 的真假判斷
下一篇
重新認識 JavaScript: Day 10 函式 Functions 的基本概念
系列文
重新認識 JavaScript37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
2
javascript
iT邦新手 2 級 ‧ 2017-12-13 09:13:03
  if( lottery.indexOf( n ) !== -1 ){
    lottery.push( n );
  }

應該是 lottery.indexOf( n ) === -1 才對
不然一直無窮迴圈下去了。

Kuro Hsu iT邦新手 1 級 ‧ 2017-12-13 09:16:36 檢舉

哈哈哈,謝謝提醒,註解打不存在就下意識寫 !== 了,已修正

不客氣,也感謝你的文章讓我可以學習並複習。

1
VagrantPi
iT邦新手 5 級 ‧ 2018-03-19 17:05:29

範例中好像有錯字

var buy = "十顆包子";

if( 看到賣西瓜的 ) {
  buy = "一個包子";  <= 一個西瓜才對
}
chiayinin iT邦新手 4 級 ‧ 2020-08-23 15:22:58 檢舉

本題工程師解析老婆的話的意思為:
1.買十個包子
2.如果「看到賣西瓜的」,改買一個包子即可。
「看到賣西瓜的」為條件,並非執行買一個西瓜唷~。

iT邦新手 2 級 ‧ 2022-03-28 10:00:08 檢舉

你是正常人的腦袋XD

1
noway
iT邦研究生 4 級 ‧ 2019-08-25 10:49:37

您好:
請問
for (var i = 1; i <= 10; i++) {


for ( i = 1; i <= 10; i++) {

最後的 差異是?
謝謝!

看更多先前的回應...收起先前的回應...
Kuro Hsu iT邦新手 1 級 ‧ 2019-08-26 10:53:32 檢舉

你好,要看你的 for 迴圈放在哪裡。

如果是在全域範圍下,那有沒有 var 其實沒差,因為最後 i 都會變成全域變數。 如果 for 迴圈在 function scoped 內,加了 var 就不會變成全域變數。

但以現在多數瀏覽器都支援的情況來說,建議都用 let 來作替代。

noway iT邦研究生 4 級 ‧ 2019-08-28 20:54:47 檢舉

謝謝!

noway iT邦研究生 4 級 ‧ 2019-08-28 21:04:11 檢舉

您好:不好意思
第三章,有一句話
所有沒有透過 var 宣告的變數都會自動變成全域變數。
各樣感覺 有點衝突:在function(){ 下:
var x=1 ; //非全域
y=2; //全域
}

?謝謝!

Kuro Hsu iT邦新手 1 級 ‧ 2019-08-29 12:29:05 檢舉

你好,你可以試著執行這段程式

function func(){
  var x = 1;
  y = 2;
}

func();

然後再分別 consolexy 的內容就知道什麼意思囉。

我要留言

立即登入留言