JavaScript / Control Flow

JS / 流程控制


簡介

為處理更複雜的情境,我們在這個章節要學習以下兩種程式的流程控制方式:

  • 條件分支
  • 迴圈

條件判斷式

除了基本的算術運算,我們還可以讓程式幫我們做「比較」與「邏輯」運算。當我們有特殊需要,想要在特定情況執行不同程式時,「比較」與「邏輯」這樣的運算就顯得格外重要;例如,「在 BMI 大於 25 而且身高小於 168 時,就提醒用戶需要減肥」這樣的動作,需要我們比較 BMI 與身高的大小,而且還要確認兩者同時發生。

比較運算

比較運算相當直覺;我們提供兩組資料給電腦,請他將比較後的結果提供給我們。一般比較後的結果僅有兩種:

  • true - 真 / 是 / 對
  • false - 假 / 否 / 錯

其中 truefalse 是一組特殊的資料,他們有別於「數字」與「字串」,我們稱之為「布林 ( boolean ) 」資料類別。

比方說下列運算,我們請電腦告訴我們 2 < 1 是否正確,並把計算結果存到 a_value 變數中:

var a_value = 2 < 1;

以這個例子來說,a_value 得到的值永遠是 false,因為 2 永遠都大於 1;比較常見的情況是我們比較變數裡的值,比方說:

function compare(a, b) { return a < b; }

因為變數 ab 的值隨時都可能改變,因此這個比較結果就會隨情況而變。

常見的「比較」運算指令有以下幾種:

  • > : 大於
  • >= : 大於或等於
  • < : 小於
  • <= : 小於或懂於
  • == : 等於
  • != : 不等於

要注意不同型別的資料比較會導致電腦進行資料轉型,進而可能得到意料之外的結果,這是一種即便資深工程師都很有可能忽視的問題。請隨時注意資料型態的問題避免程式發生意料之外的狀況。

邏輯運算

如前述的例子,「在 BMI 大於 25 而且身高小於 168 時」除了做了兩個比較以外,我們還需要確認兩者同時發生。「而且」可以視做一個運算,把前者 ( bmi > 25 ) 與後者 ( height < 168 ) 做一個「而且」運算。這樣的運算我們叫他「邏輯運算」,寫法是「&&」:

bmi > 25 && height < 168

與而且相對的是「或者」,可以用「||」表示:

bmi > 25 || height < 168

邏輯運算主要是用來操作布林 ( boolean ) 型態的資料,因此在其前後的資料會被轉型為布林,例如:

1 || 0

會先將 1 轉型為布林 (得到 true)、0 亦轉型為布林 (得到 false),因此運算結果最後會是 true || false,也就是 true

反向運算

除了「小於」「大於」等概念,你也可以用「不大於」、「不小於」等方式運算。在運算結果前面加上驚嘆號! 可以使運算結果的真假交換 ( 真變假、假變真 ),例如:

!(1 > 2) /* 非( 1 > 2), 得 true */ !(true && false) /* 非(真且假), 得 true */ /* 不是(身高170、月薪十萬、房子一棟以上)的話,我就不要 */ !(height > 170 && salary > 100000 && house > 1)
運算優先序

各種運算混在一起揪~竟誰該先算是件很頭痛的事,因此程式語言本身規定了各種運算的先後順序 (operator precedence )。我們不深究優先序的細節,但請大家基本上簡單記住這個規則即可:

括號 () > 反向 - > 算術 + - * / > 邏輯 && ||

條件分支

我們可以叫電腦在符合某些條件時才執行特定程式。我們只需要使用 if else 語法:

var a_value = true; var b_value = true; if(a_value) { /* a_value 為真的話, 這裡會執行 */ } else if (b_value) { /* 如果 a_value 為假, 但b_value 為假的話, 這裡會執行 */ } else { /* 若上述判斷都為假, 就會執行這裡 */ }

為了試試效果,我們來做做看下面的練習吧!

體重過重警告

在基本語法的練習中,我們嘗試了 BMI 的計算。為了加強我們計算機的功能,我們試著用 if / else 來加入過重警告吧!

比方說,在 bmi 超過 25 時,使用 alert 函式提醒用戶:

var getbmi = function() { var value = +weight.value / (+height.value * +height.value) if(value >= 25) { alert("您的體重太重啦!"); } bmi.innerText = value; }; weight.onkeyup = getbmi; height.onkeyup = getbmi;

您能基於以上程式碼,再加入一個體重 ( bmi < 18 ) 過輕的警告嗎?

程式註解

在上面的練習中,我們有時候會在程式碼中看到像這樣、由斜線 / 與星號 * 組成的語法:

/* ... */

這其實是程式的註解,標記在 /**/ 之間的內容為「註解」,是寫給人看的文字描述,電腦會跳過這個段落不執行它。

動畫

為了讓東西動起來,我們需要不斷的執行指令來更新元素的位置等屬性,但是像這樣的程式是不行的:

/* node 是某個 div */ node.style.left = "10px"; node.style.left = "20px"; node.style.left = "30px"; node.style.left = "40px";

雖然看起來好像更新了四次,但瀏覽器執行我們的指令會一次執行完以後,才開始重新繪製網網頁上的元素,也因此我們只會看到 node 瞬間跳到了 40px 的位置。

不過瀏覽器提供了一個方便的函式 requestAnimationFrame,用來接收更新畫面的程式。他接收一個函式做為參數,並會在適當的時機幫你執行該函式:

var myAnimation = function(t) { node.style.left = "10px"; }; requestAnimationFrame(myAnimation);

我們可以在函式裡再次呼叫 requestAnimationFrame,利用類似「自我呼叫」的方式達成無限重繪的循環:

var myAnimation = function(t) { node.style.left = "10px"; requestAnimationFrame(myAnimation); }; requestAnimationFrame(myAnimation);

注意到我們的自訂函式 myAnimation 有接受一個參數 t,那是 requestAnimationFrame 會主動提供給我們的「時間」資訊 ( 單位 ms ),可以簡單想像成「目前播放多久了」的概念。

我們可以試著利用時間 t 來替標籤更新位置。簡單的使用餘數運算%

var myAnimation = function(t) { node.style.left = (t % 100) + "px"; requestAnimationFrame(myAnimation); }; requestAnimationFrame(myAnimation);
奔跑的小童

利用基本語法中的小童搭配 requestAnimationFrame 來嘗試做移動動畫吧!你能讓小童跑慢一點嗎?

角色控制

上例直接使用時間來幫小童定位,但一般來說我們會想自行控制角色的位置。我們需要先定義一組變數儲存座標:

var player = { x: 0, y: 0 };

然後在繪製小童時,使用這組變數當做他的 lefttop 樣式值:

var myAnimation = function(t) { requestAnimationFrame(myAnimation); node.style.left = player.x + "px"; node.style.top = player.y + "px"; }

接著,在鍵盤事件時觸發座標變換。由於方向有四種,我們需要用到條件判斷式搭配基本語法中提到的鍵盤事件來使用。

相對於前面使用的 onkeyup,我們希望玩家按著鍵盤時角色就會移動,因此我們需要使用 onkeydown 事件:

document.onkeydown = function(evt) { ... };

還記得 evt.keyCode 對應到的方向鍵值嗎?

  • 37: 左
  • 38: 上
  • 39: 右
  • 40: 下

我們先試試看往右移動吧!當 evt.keyCode == 39 時, player.x 的值要增加 1

var player = {x: 0, y: 0}; document.onkeydown = function(evt) { if(evt.keyCode == 39) { player.x = player.x + 1; } };

接下來的部份你可以把他完成嗎?

控制小童

請把移動控制的部份完成、搭配前面實作的動畫循環來做出下列效果:

螢幕跟著捲動了,怎麼辦?

當你用方向鍵控制小童時,上下鍵會導致網頁捲動,很不方便。能不能不要讓他捲動呢?

在事件控制函式的 evt 參數中提供了一個函式,名字剛好就解釋了其用途:

preventDefault - 避免預設行為

預設行為即此事件發生時、瀏覽器原本預期要執行的動作 ( 比方說點擊連結跳轉、右鍵跳出選單、上下移動捲軸等 ),因此我們只要在按鍵事件中執行這個函式,原本預期的行為 ( 也就是捲動 ) 就不會發生了:

document.onkeydown = function(evt) { evt.preventDefault(); }

要注意這樣會擋住所有按鍵的預設行為,所以如果只想要避免捲動,我們可以使用條件判斷式,在按上、下時再執行 preventDefault 即可:

document.onkeydown = function(evt) { if(evt.keyCode == 38 || evt.keyCode == 40) { evt.preventDefault(); } }
跳躍的小童

試著加上跳躍的動作吧!你有辦法讓玩家按空白鍵 ( keyCode 32 ) 時,讓小童原地跳一下嗎?

為了要實作跳躍,我們要簡單模擬重力。重力簡單的說就是向下的加速度,於是我們需要在 player 物件中額外加上一個速度變數 v

var player = { x: 0, y: 0, v: 0, };

並在跳躍時給小童一個瞬間向上的速度,用變數 v 代表:

document.onkeydown = function(evt) { if(evt.keyCode == 32) { player.v = -5; } }

同時隨時不斷的以重力更新速度、再以速度更新座標、再設定一個地板條件讓他著陸:

var myAnimation = function(t) { requestAnimationFrame(myAnimation); player.v = player.v + 0.1; player.y = player.y + player.v; if(player.y > 0) { player.v = 0; player.a = 0; } }

以上僅為部份程式碼,細節留給你完成吧!下圖即為範例結果,試著按按看空白鍵即可以看到跳躍效果:

新媒體內容技術與設計

New Media Techniques and Design