流程控制與例外處理

JavaScript 擁有許多陳述式,特別是流程控制的陳述式,你可以用這些陳述式來增加程式的互動性。這個章節將會概要介紹陳述式。

JavaScript 參考中有比本章更多關於陳述式的細節。 在 Javascript 程式碼中,分號(;)被用來隔開陳述式。

任何 JavaScript 運算式也是一個陳述式。 有關運算式的完整資訊,請參閱運算式與運算子

區塊陳述式

最基本的陳述式是「用於將陳述式分塊」的「區塊陳述式」。程式區塊被以一對大括號隔離開來:

{
  陳述式 1;
  陳述式 2;
  .
  .
  .
  陳述式 n;
}

範例

區塊陳述式經常與流程控制陳述式(例如:ifforwhile)搭配使用。

js
while (x < 10) {
  x++;
}

在這裏,{ x++; } 是區塊陳述式。

重要:JavaScript 在 ECMAScript2015 (第六版)以前的版本並沒有區塊範圍的概念。 在區塊中的變數有效範圍是包含該區塊的函式或者是執行檔,並且在區塊外也可使用它們。換句話說,區塊並不定義了範圍。JavaScript 中的"獨立"區塊將會產生與 C 和 Java 中不同的效果。舉例來說:

js
var x = 1;
{
  var x = 2;
}
console.log(x); // 輸出 2

這裏輸出了 2 是因為區塊中變數 var x 陳述式擁有與區塊外的 var x 相同的有效範圍。在 C 或 Java,相同的程式碼將會輸出 1。

從 ECMAScript2015 版本起, 使用 let 定義的變數範圍將被限制於區塊內。

條件陳述式

條件陳述式是一些在指定條件爲真下將被執行的指令。 JavaScript 支援兩種條件陳述式: if...elseswitch

if...else 陳述式

if 陳述式將在「邏輯判斷爲真」下執行接下來的一個陳述式。選擇性的 else 陳述式將會在「邏輯判斷爲否」時被執行。 if 陳述式的用法看起來如下:

if (指定條件) {
  陳述式 1;
} else {
  陳述式 2;
}

指定條件可以是任何會回傳 true 或 false 的運算式。參見 Boolean 來進一步瞭解哪些運算式會回傳 truefalse。假如指定條件爲 true,陳述式 1 會被執行;否則,陳述式 2 會被執行。陳述式 1 及陳述式 2 可以是任何陳述式,包含巢狀 if 陳述式。

你也可以藉由 else if 來使用複合的陳述式來測試多種不同的條件,如下:

if (指定條件1) {
  陳述式 1;
} else if (指定條件 2) {
  陳述式 2;
} else if (指定條件 n) {
  陳述式 n;
} else {
  最後陳述式;
}

在多個條件中,只有第一個條件爲 true 的陳述式會被執行。若要執行多個陳述式,可以將陳述式包在同一個程式區塊中({ ... })。 通常來說,使用區塊陳述式是很好的習慣,尤其在使用巢狀 if 的時候:

if (指定條件) {
  陳述式_1_執行_當_指定條件_爲_真;
  陳述式_2_執行_當_指定條件_爲_真;
} else {
  陳述式_3_執行_當_指定條件_爲_否;
  陳述式_4_執行_當_指定條件_爲_否;
}

建議不要在以賦值作爲條件運算式,因為"賦值"常常被和"等於"搞混。 例如, 不要寫出如下面的程式碼:

js
if (x = y) {
  /* 陳述式 */
}

如果你真的需要以賦值作爲條件運算式,一種常見的方式是額外在賦值式外面加上一組括號。例如:

js
if ((x = y)) {
  /* 陳述式 */
}

爲"否"的值

下列的值會被看作 false(也稱為 Falsy 值)

  • false
  • undefined
  • null
  • 0
  • NaN
  • 空字串("")

其他所有的值,包含所有物件,當被作為條件陳述式都會被視為 true

不要把"布林真假值"和"布林物件的真假值"弄混了。 例如:

js
var b = new Boolean(false);
if (b) // 這會是 True
if (b == true) // 這會是 false

範例

在下面的範例中,函式 checkData 回傳 trueText 物件的長度爲三;否則,顯示出 alert 並回傳 false

js
function checkData() {
  if (document.form1.threeChar.value.length == 3) {
    return true;
  } else {
    alert(
      "請輸入恰好三個字元. " +
        document.form1.threeChar.value +
        " is not valid.",
    );
    return false;
  }
}

switch 陳述式

switch 陳述式允許程式依運算式的不同值來選擇不同標籤。 假如運算式和標籤匹配,程式會執行標籤對應的陳述式。範例如下:

switch (運算式) {
  case 標籤 1:
    陳述式 1
    [break;]
  case 標籤 2:
    陳述式 2
    [break;]
    ...
  default:
    陳述式
    [break;]
}

程序首先尋找一個標籤與運算式的值匹配的 case 子句,然後將控制權轉移給該子句,執行與其相關的陳述式。 如果沒有找到匹配的標籤,程序將查找 default 子句(選擇性),如果找到,則將控制權轉移到該子句,執行關聯的陳述式。 如果沒有找到 default 子句,程序繼續在 switch 結束後的陳述式執行。 按照慣例,默認子句是最後一個子句,但並不硬性規定。

與每個 case 子句相關聯的 break 陳述式(選擇性)確保程序在發現匹配的陳述式之後退出 switch,並在 switch 後的陳述式中繼續執行。 如果省略 break,程序將繼續在 switch 陳述式中的下一個陳述式執行。

範例

在下面範例中,如果變數 fruittype 為「Bananas」,程序將與「Bananas」匹配並執行相關陳述式。 當遇到 break 時,程序離開 switch 並執行 switch 後的陳述式。 如果省略 break,也將執行 case 「Cherries」的陳述式。

js
switch (fruittype) {
  case "Oranges":
    console.log("Oranges are $0.59 a pound.");
    break;
  case "Apples":
    console.log("Apples are $0.32 a pound.");
    break;
  case "Bananas":
    console.log("Bananas are $0.48 a pound.");
    break;
  case "Cherries":
    console.log("Cherries are $3.00 a pound.");
    break;
  case "Mangoes":
    console.log("Mangoes are $0.56 a pound.");
    break;
  case "Papayas":
    console.log("Mangoes and papayas are $2.79 a pound.");
    break;
  default:
    console.log("Sorry, we are out of " + fruittype + ".");
}
console.log("Is there anything else you'd like?");

例外處理陳述式

你可以用以 throw 陳述式丟出例外,並以 try...catch 陳述式處理之。

例外的形態

任何物件(object)都可以在 JavaScript 中被拋出。 然而,並非所有拋出的物件都相同。 雖然將數字或字串作為錯誤物件使用是相當常見的,但使用為此目的專門創造的一種例外物件類型通常更有效:

throw 陳述式

使用 throw 陳述式拋出例外。當拋出例外時,你要指定包含在要拋出物件中的值:

throw expression;

你可以拋出任何運算式,而不僅僅是特定類型的運算式。以下的程式碼會拋出一些不同類型的例外:

js
throw "Error2"; // 字串形態
throw 42; // 數字形態
throw true; // True/False
throw {
  toString: function () {
    return "我是物件!";
  },
};

备注: 你可以在拋出例外時指定物件。然後,可以在 catch 區塊中引用對象的屬性。

js
// 創建類型爲 UserException 的物件
function UserException(message) {
  this.message = message;
  this.name = "UserException";
}

// 讓例外轉換成整齊的字串當它被當作字串使用時
// (舉例來說:於 error console)
UserException.prototype.toString = function () {
  return this.name + ': "' + this.message + '"';
};

// 創建一個物件的實例並丟出它
throw new UserException("Value too high");

try...catch 陳述式

try...catch 陳述式標記了一組要嘗試的陳述式,並在拋出例外時指定一個或多個響應。 如果例外被拋出,try...catch 陳述式捕獲它。

try...catch 陳述式包括一個 try 區塊,它包含一個或多個陳述式,零個或多個 catch 區塊,包含在 try 區塊中拋出例外時該做什麼的陳述式。 也就是說,你希望 try 區塊成功,如果它不成功,你希望控制權傳遞給 catch 區塊。 如果 try 區塊內的任何陳述式(或在 try 區塊內調用的函數中)拋出例外,則控制立即切換到 catch 區塊。 如果在 try 區塊中沒有拋出例外,則跳過 catch 區塊。 finally 區塊在 trycatch 區塊執行後執行,但在 try...catch 陳述式之後的陳述式之前執行。

以下的範例使用 try...catch 陳述式。該範例調用基於傳遞給函數的值並從陣列中檢索月份名稱的函數。如果值不對應於月份數(1-12),則會拋出一個例外,其值為 "InvalidMonthNo",並且 catch 區塊中的陳述式將 monthName 變數設置為 unknown

js
function getMonthName(mo) {
  mo = mo - 1; // Adjust month number for array index (1 = Jan, 12 = Dec)
  var months = [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
  ];
  if (months[mo]) {
    return months[mo];
  } else {
    throw "InvalidMonthNo"; //throw 關鍵字在這裏被使用
  }
}

try {
  // statements to try
  monthName = getMonthName(myMonth); // 函式可以丟出例外
} catch (e) {
  monthName = "unknown";
  logMyErrors(e); // 將例外傳至例外處理機制
}

catch 區塊

你可以使用 catch 區塊來處理 try 區塊可能丟出的例外。

catch (catchID) {
  陳述式
}

catch 區塊指定用來保存 throw 陳述式所丟出的值的標識符(前面語法中的 catchID) 你可以使用此標識符獲取有關被拋出的例外的信息。 JavaScript 在進入catch 區塊時創建此標識符; 標識符僅持續 catch 區塊的持續時間;在 catch 區塊完成執行後,標識符不再可用。

例如,下列的程式碼中丟出了一個例外,當例外發生後,控制權被轉交給 catch 區塊。

js
try {
  throw "myException"; // 產生例外
} catch (e) {
  // 用於處理例外的陳述式
  logMyErrors(e); // 將例外物件傳給 error handler
}

finally 區塊

finally 區塊中包含在 trycatch 區塊執行之後但在 try...catch 陳述式之後的陳述式之前 執行的陳述式。 無論是否拋出例外,finally 區塊都會執行。 如果拋出例外,則即使沒有 catch 區塊處理例外,finally 區塊中的陳述式也會執行。

你可以使用 finally 區塊來使腳本在發生例外時正常地結束。例如,你可能需要釋放腳本中綁定的資源。 在以下示例中,打開一個文件,然後執行使用該文件的陳述式(伺服器端 JavaScript 允許你訪問文件)。 如果在打開文件時拋出例外,finally 區塊會在腳本結束之前關閉文件。

js
openMyFile();
try {
  writeMyFile(theData); // 可能產生例外
} catch (e) {
  handleError(e); // 處理可能發生的例外
} finally {
  closeMyFile(); // 總是在 try 結束後關閉檔案
}

如果 finally 區塊有返回值,那麼該值將成為整個 try-catch-finally 過程的返回值,而捨棄 trycatch 區塊中的任何返回陳述式:

js
function f() {
  try {
    console.log(0);
    throw "bogus";
  } catch (e) {
    console.log(1);
    return true; // 這個回傳會被擱置
    // 直到 finally 區塊結束
    console.log(2); // 不會到達這裏
  } finally {
    console.log(3);
    return false; // 覆寫先前的 "return"
    console.log(4); // 不會到達這裏
  }
  // "return false" 在這裏被執行
  console.log(5); // 不會到達這裏
}
f(); // console 0, 1, 3; 會回傳false

finally 區塊覆寫返回值也適用於在 catch 區塊中拋出或重新拋出的例外(即便在catch 中再次丟出例外,catch 所屬的 finally 區塊還是會被執行):

js
function f() {
  try {
    throw "bogus";
  } catch (e) {
    console.log('caught inner "bogus"');
    throw e; // 此處的 throw 陳述式將被擱置到
    // finally 區塊結束
  } finally {
    return false; // 覆寫先前的"throw"
  }
  // "return false" 在此被執行
}

try {
  f();
} catch (e) {
  // 這裏永遠不可能到達因為在 f 函式中 catch 的 throw
  // 被 finally 中的 return 覆寫了
  console.log('caught outer "bogus"');
}

// 輸出 -> caught inner "bogus"

巢狀 try...catch 陳述式

你可以使用一個或多個的 try...catch 陳述式。 假如一個內層的try...catch 陳述式不具有 catch 區塊, 它將必須要有 finally 區塊與及封閉的 try...catch 陳述式來檢測是否有符合的例外。

使用 Error 物件

根據錯誤的類型,你可以使用 "name" 和 "message" 屬性來獲取更精確的資訊。"name" 提供了錯誤所屬的類別(class)(例如,"DOMException" 或 "Error"),而 "message" 通常提供藉由將錯誤物件轉換為字串所獲得的更簡潔的資訊。參見巢狀 try 區塊位於 try...catch 參考資料頁面。

假如你要丟出自定義的例外, 為了方便使用這些屬性(例如,如果你的 catch 區塊並不要區分你自己的例外和系統的),你可以使用 Error 構造子。舉例來說:

js
function doSomethingErrorProne () {
  if (ourCodeMakesAMistake()) {
    throw (new Error('The message'));
  } else {
    doSomethingToGetAJavascriptError();
  }
}
....
try {
  doSomethingErrorProne();
}
catch (e) {
  console.log(e.name); // 紀錄 'Error'
  console.log(e.message); // 紀錄 'The message' 或者其他 JavaScript 例外的資訊)
}

Promises 容器

從 ECMAScript2015 起,JavaScript 支援 Promise 物件,允許你控制延遲和異步操作的流程。

Promise 有以下幾種狀態:

  • pending:等待中,為初始之狀態,即不是 fulfilled 也不是 rejected。
  • fulfilled:已實現,表示操作成功完成。
  • rejected:已拒絕,表示操作失敗。
  • settled:已完成,表示 Promise 狀態為已實現或已拒絕,但不是等待中。

使用 XHR 載入圖檔

這裏有個簡單的範例,使用了 Promise 物件與及 XMLHttpRequest 來載入 MDN GitHub promise-test repository 中的一張圖檔。你也可以觀看結果。 每一步都有註解來讓你慢慢理解 Promise 物件與及 XHR 架構。 下面的版本沒有註解,但藉由觀察 Promise 物件的變動你或許可以對 promise 物件有所了解:

js
function imgLoad(url) {
  return new Promise(function (resolve, reject) {
    var request = new XMLHttpRequest();
    request.open("GET", url);
    request.responseType = "blob";
    request.onload = function () {
      if (request.status === 200) {
        resolve(request.response);
      } else {
        reject(
          Error(
            "Image didn't load successfully; error code:" + request.statusText,
          ),
        );
      }
    };
    request.onerror = function () {
      reject(Error("There was a network error."));
    };
    request.send();
  });
}

以獲得更多資訊,查看 Promise 參照頁面,以及使用 Promises 教學。