《JavaScript 闖關記》之語句

劼哥stone發表於2016-10-30

表示式在 JavaScript 中是短語,那麼語句就是整句命令。表示式用來計算出一個值,語句用來執行以使某件事發生。從本質上看,語句定義了 JavaScript 中的主要語法,語句通常使用一或多個關鍵字來完成給定任務。語句可以很簡單,例如通知函式退出;也可以比較複雜,例如指定重複執行某個命令的次數。下表列出了 JavaScript 大部分語句的語法和用途:

語句 語法 用途
break break [label]; 退出最內層迴圈或者退出 switch 語句,又或者退出 label 指定的語句
case case expression: switch 語句中標記一條語句
continue continue [label]; 重新開始最內層的迴圈或重新開始 label 指定的迴圈
debugger debugger; 斷點器除錯
default default; switch 中標記預設的語句
do-while do statement while(expression); while 迴圈的一種替代形式
empty ; 什麼都不做
for for(init;expr;incr) statement 簡寫的迴圈結構
for-in for(var in object) statement 遍歷一個物件的屬性
function function name([param[],...])
{statement}
宣告一個函式
if-else if (expression) statement1
[else statement2]
執行 statement1 或者 statement2
label label:statement statement 指定一個名字 label
return return [expression]; 從函式返回一個值
switch switch(expression){statement} case 或者 default 語句標記的多分支語句
throw throw expression; 丟擲異常
try try {statement}
[catch {handler statement}]
[finally {cleaup statement}]
捕獲異常
use strict "use strict" 對指令碼和函式應用嚴格模式
var var name=[=expr][,...]; 宣告並初始化一個或多個變數
while while(expression) statement 基本的迴圈結構
with with(object) statement 擴充套件作用域鏈

條件語句

if-else 語句

大多數程式語言中最為常用的一個語句就是 if-else 語句。以下是 if-else 語句的語法:

if (condition) statement1 [else statement2]複製程式碼

其中的 condition 可以是任意表示式;而且對這個表示式求值的結果不一定是布林值。JavaScript 會自動呼叫 Boolean() 轉換函式將這個表示式的結果轉換為一個布林值。如果對 condition 求值的結果是 true,則執行 statement1,如果對 condition 求值的結果是 false,則執行 statement2。而且這兩個語句既可以是一行程式碼,也可以是一個程式碼塊(以一對花括號括起來的多行程式碼)。請看下面的例子:

if (i > 25)
    console.log("Greater than 25.");              // 單行語句
else {
    console.log("Less than or equal to 25.");     // 程式碼塊中的語句
}複製程式碼

業界普遍推崇的最佳實踐是始終使用程式碼塊,即使要執行的只有一行程式碼。因為這樣可以消除人們的誤解,否則可能讓人分不清在不同條件下要執行哪些語句。

switch 語句

switch 語句與 if 語句的關係最為密切,而且也是在其他語言中普遍使用的一種流控制語句。JavaScript 中 switch 語句的語法與其他基於 C 的語言非常接近,如下所示:

switch (expression) {
  case value: statement
    break;
  case value: statement
    break;
  case value: statement
    break;
  case value: statement
    break;
  default: statement
}複製程式碼

switch 語句中的每一種情形的含義是:“如果表示式等於這個值(value),則執行後面的語句(statement)”。而 break 關鍵字會導致程式碼執行流跳出 switch 語句。如果省略 break 關鍵字,就會導致執行完當前 case 後,繼續執行下一個 case。最後的 default 關鍵字則用於在表示式不匹配前面任何一種情形的時候,也相當於一個 else 語句。從根本上講,switch 語句就是為了讓開發人員免於編寫像下面這樣的程式碼:

if (i === 25){
  console.log("25");
} else if (i === 35) {
  console.log("35");
} else if (i === 45) {
  console.log("45");
} else {
  console.log("Other");
}複製程式碼

而與此等價的switch語句如下所示:

switch (i) {
    case 25: 
        console.log("25");
        break;
    case 35: 
        console.log("35");
        break;
    case 45: 
        console.log("45");
        break;
    default: 
        console.log("Other");
}複製程式碼

通過為每個case後面都新增一個break語句,就可以避免同時執行多個case程式碼的情況。假如確實需要混合幾種情形,不要忘了在程式碼中新增註釋,說明你是有意省略了break關鍵字。

雖然 JavaScript 中的 switch 語句借鑑自其他語言,但這個語句也有自己的特色。首先,可以在 switch 語句中使用任何資料型別(在很多其他語言中只能使用數值),無論是字串,還是物件都沒有問題。其次,每個 case 的值不一定是常量,可以是變數,甚至是表示式。請看下面這兩個例子:

switch ("hello world") {
    case "hello" + " world": 
        console.log("Greeting was found.");
        break;
    case "goodbye": 
        console.log("Closing was found.");
        break;
    default: 
        console.log("Unexpected message was found.");
}

var num = 25;
switch (true) {
    case num < 0: 
        console.log("Less than 0.");
        break;
    case num >= 0 && num <= 10: 
        console.log("Between 0 and 10.");
        break;
    case num > 10 && num <= 20: 
        console.log("Between 10 and 20.");
        break;
    default: 
        console.log("More than 20.");
}複製程式碼

switch 語句首先計算 switch 關鍵字後的表示式,然後按照從上到下的順序計算每個 case 後的表示式,直到執行到 case 的表示式的值與 switch 的表示式的值相等時為止。由於對每個 case 的匹配操作實際上是 === 恆等運算子比較,而不是 == 相等運算子比較,因此,表示式和 case 的匹配並不會做任何型別轉換。

迴圈

while 語句

while 語句屬於前測試迴圈語句,也就是說,在迴圈體內的程式碼被執行之前,就會對出口條件求值。因引,迴圈體內的程式碼有可能永遠不會被執行。以下是 while 語句的語法:

while(expression) statement複製程式碼

下面是一個示例:

var i = 0;
while (i < 10) {
    i += 2;
}複製程式碼

do-while 語句

do-while 語句是一種後測試迴圈語句,即只有在迴圈體中的程式碼執行之後,才會測試出口條件。換句話說,在對條件表示式求值之前,迴圈體內的程式碼至少會被執行一次。以下是 do-while 語句的語法:

do {
    statement
} while (expression);複製程式碼

下面是一個示例:

var i = 0;
do {
   i += 2;
} while (i < 10);複製程式碼

for 語句

for 語句也是一種前測試迴圈語句,但它具有在執行迴圈之前初始化變數和定義迴圈後要執行的程式碼的能力。以下是 for 語句的語法:

for (initialization; expression; post-loop-expression) statement複製程式碼

下面是一個示例:

var count = 10;
for (var i = 0; i < count; i++){
    console.log(i);
}複製程式碼

這個 for 迴圈語句與下面的 while 語句的功能相同:

var count = 10;
var i = 0;
while (i < count){
    console.log(i);
    i++;
}複製程式碼

由於 JavaScript 中不存在塊級作用域,因此在迴圈內部定義的變數也可以在外部訪問到。例如:

var count = 10;
for (var i = 0; i < count; i++){
    console.log(i);
}
console.log(i); // 10複製程式碼

此外,for 語句中的初始化表示式、控制表示式和迴圈後表示式都是可選的。將這兩個表示式全部省略,就會建立一個無限迴圈,例如:

// 無限迴圈
for (;;) {
    doSomething();
}複製程式碼

for-in 語句

for-in 語句是一種精準的迭代語句,可以用來列舉物件的屬性。以下是 for-in 語句的語法:

for (property in object) statement複製程式碼

下面是一個示例:

for (var propName in window) {
    console.log(propName);
}複製程式碼

在這個例子中,我們使用 for-in 迴圈來顯示了 BOM 中 window 物件的所有屬性。每次執行迴圈時,都會將 window 物件中存在的一個屬性名賦值給變數 propName。這個過程會一直持續到物件中的所有屬性都被列舉一遍為止。與 for 語句類似,這裡控制語句中的 var 操作符也不是必需的。但是,為了保證使用區域性變數,我們推薦上面例子中的這種做法。

JavaScript 物件的屬性沒有順序。因此,通過 for-in 迴圈輸出的屬性名的順序是不可預測的。具體來講,所有屬性都會被返回一次,但返回的先後次序可能會因瀏覽器而異。

如果表示要迭代的物件的變數值為 nullundefinedfor-in 語句會丟擲錯誤。雖然 ECMAScript 5更正了這一行為;對這種情況不再丟擲錯誤,而只是不執行迴圈體。為了保證最大限度的相容性,建議在使用 for-in 迴圈之前,先檢測確認該物件的值不是 nullundefined

跳轉

label 語句

使用 label 語句可以在程式碼中新增標籤,以便將來使用。以下是 label 語句的語法:

label: statement複製程式碼

下面是一個示例:

start: for (var i=0; i < count; i++) {
    console.log(i); 
}複製程式碼

這個例子中定義的 start 標籤可以在將來由 breakcontinue 語句引用。加標籤的語句一般都要與 for 語句等迴圈語句配合使用。

breakcontinue 語句

breakcontinue 語句用於在迴圈中精確地控制程式碼的執行。其中,break 語句會立即退出迴圈,強制繼續執行迴圈後面的語句。而 continue 語句雖然也是立即退出迴圈,但退出迴圈後會從迴圈的頂部繼續執行。請看下面的例子:

var num = 0;

for (var i=1; i < 10; i++) {
    if (i % 5 == 0) {
       break;
    }
    num++;
}

console.log(num);   // 4複製程式碼

這個例子中的 for 迴圈會將變數 i 由1遞增至 10。在迴圈體內,有一個 if 語句檢查 i 的值是否可以被 5 整除(使用求模運算子)。如果是,則執行 break 語句退出迴圈。另一方面,變數 num0 開始,用於記錄迴圈執行的次數。在執行 break 語句之後,結果顯示 4。也就是說,在變數 i 等於 5 時,迴圈總共執行了 4 次;而 break 語句的執行,導致了迴圈在 num 再次遞增之前就退出了。如果在這裡把 break 替換為 continue 的話,則可以看到另一種結果:

var num = 0;

for (var i=1; i < 10; i++) {
if (i % 5 == 0) {
        continue;
    }
    num++;
}

console.log(num);   // 8複製程式碼

例子的結果顯示 8,也就是迴圈總共執行了 8 次。當變數 i 等於 5 時,迴圈會在 num 再次遞增之前退出,但接下來執行的是下一次迴圈,即i的值等於 6 的迴圈。於是,迴圈又繼續執行,直到 i 等於 10 時自然結束。而 num 的最終值之所以是 8,是因為 continue 語句導致它少遞增了一次。

breakcontinue 語句都可以與 label 語句聯合使用,從而返回程式碼中特定的位置。這種聯合使用的情況多發生在迴圈巢狀的情況下,如下面的例子所示:

var num = 0;

outermost:
for (var i = 0; i < 10; i++) {
     for (var j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            break outermost;
        }
        num++;
    }
}

console.log(num);   // 55複製程式碼

在這個例子中,outermost 標籤表示外部的 for 語句。如果每個迴圈正常執行 10 次,則 num++ 語句就會正常執行 100 次。換句話說,如果兩個迴圈都自然結束,num 的值應該是 100。但內部迴圈中的 break 語句帶了一個引數:要返回到的標籤。新增這個標籤的結果將導致 break 語句不僅會退出內部的 for 語句(即使用變數 j 的迴圈),而且也會退出外部的 for 語句(即使用變數 i 的迴圈)。為此,當變數 ij 都等於 5 時, num的值正好是 55。同樣,continue 語句也可以像這樣與 label 語句聯用,如下面的例子所示:

var num = 0;

outermost:
for (var i = 0; i < 10; i++) {
    for (var j = 0; j < 10; j++) {
        if (i == 5 && j == 5) {
            continue outermost;
        }
        num++;
    }
}

console.log(num);   // 95複製程式碼

在這種情況下,continue 語句會強制繼續執行迴圈,退出內部迴圈,執行外部迴圈。當 j5 時,continue 語句執行,而這也就意味著內部迴圈少執行了 5 次,因此 num 的結果是 95

雖然聯用 breakcontinuelabel 語句能夠執行復雜的操作,但如果使用過度,也會給除錯帶來麻煩。在此,我們建議如果使用 label 語句,一定要使用描述性的標籤,同時不要巢狀過多的迴圈。

return 語句

return 語句的作用是指定函式呼叫後的返回值。return 語句的語法如下:

return [expression];複製程式碼

下面是一個示例:

function square(x) { return x*x; }  // 一個包含 return 語句的函式
square(2);                          // 呼叫結果為 4複製程式碼

return 語句只能在函式體內出現,如果不是的話會報語法錯誤。當執行到 return 語句的時候,函式終止執行,並返回 expression 的值給呼叫程式。如果沒有 return 語句,則函式呼叫僅依次執行函式體內的每一條語句直到函式結束,最後返回撥用程式。這種情況下,呼叫表示式的結果是 undefinedreturn 語句經常作為函式內的最後一條語句出現,但並不是說要一定放在函式最後。return 語句可以單獨使用而不必帶有 expression,這樣的話函式也會向呼叫程式返回 undefined

由於 JavaScript 可以自動插入分號,因此在 return 關鍵字和它後面的表示式之間不能有換行。

throw 語句

throw 語句的作用是把程式執行時產生的錯誤顯式地丟擲異常。throw 語句的語法如下:

throw expression;複製程式碼

expression 的值可以是任意型別的。可以丟擲一個代表錯誤碼的數字,或者包含可讀的錯誤訊息的字串。當 JavaScript 直譯器丟擲異常的時候通常採用 Error 型別和其子型別。Error 物件有一個 name 屬性表示錯誤型別,一個 message 屬性用來存放傳遞給建構函式的字串,在下面的例子中,當使用非法引數呼叫函式時就丟擲一個 Error 物件:

function factorial(x) {
// 如果輸入引數是非法的,則丟擲一個異常
    if (x < 0) throw new Error("x不能是負數");
    // 否則,計算出一個值,並正常地返回它
    for(var f = 1; x > 1; f *= x, x--) /* empty */ ;
    return f;
}複製程式碼

當丟擲異常時,JavaScript 直譯器會立即停止當前正在執行的邏輯,並跳轉至就近的異常處理程式。如果沒有找到任何異常處理程式,異常就會沿著 JavaScript 方法的詞法結構和呼叫棧向上傳播。最終 JavaScript 將把異常當成程式錯誤來處理,並報告給使用者。

try 語句

try-catch-finally 語句是 JavaScript 中異常處理機制,try-catch-finally 語句的語法如下:

try {statement} [catch {handler statement}] [finally {cleaup statement}]複製程式碼

try 從句定義了需要處理的異常所在的程式碼塊。catch 從句跟隨在 try 從句之後,當 try 塊內某處發生了異常時,呼叫 catch 內的程式碼邏輯。catch 從句後跟隨 finally 塊,後者中放置清理程式碼,不管 try 塊中是否產生異常,finally 塊內的邏輯總是會執行。儘管 catchfinally 都是可選的,但 try 從句需要至少二者之一與之組成完整的語句。trycatchfinally 語句塊都需要使用花括號括起來,這裡的花括號是必需的,即使從句中只有一條語句也不能省略花括號。

下面的程式碼詳細的說明了 try-catch-finally 的使用目的:

try {
    // 通常來講,這裡的程式碼會從頭執行到尾而不會產生任何問題,
    // 但有時會丟擲一個異常,要麼是由 throw 語句直接丟擲異常,
    // 要麼是通過呼叫一個方法間接丟擲異常
}
catch(e) {
    // 當且僅當 try 語句塊丟擲了異常,才會執行這裡的程式碼
    // 這裡可以通過區域性變數 e 來獲得對 Error 物件或者丟擲的其他值的引用
    // 這裡的程式碼塊可以基於某種原因處理這個異常,也可以忽略這個異常,
    // 還可以通過 throw 語句重新丟擲異常
}
finally {
    // 不管 try 語句塊是否丟擲了異常,這裡的邏輯總是會執行,終止 try 語句塊的方式有:
    // 1)正常終止,執行完語句塊的最後一條語句
    // 2)通過 break、continue 或 return 語句終止
    // 3)丟擲一個異常,異常被 catch 從句捕獲
    // 4)丟擲一個異常,異常未被捕獲,繼續向上傳播
}複製程式碼

其他

with 語句

with 語句的作用是將程式碼的作用域設定到一個特定的物件中。with 語句的語法如下:

with (expression) statement;複製程式碼

定義 with 語句的目的主要是為了簡化多次編寫同一個物件的工作,如下面的例子所示:

var qs = location.search.substring(1);
var hostName = location.hostname;
var url = location.href;複製程式碼

上面幾行程式碼都包含 location 物件。如果使用 with 語句,可以把上面的程式碼改寫成如下所示:

with(location){
    var qs = search.substring(1);
    var hostName = hostname;
    var url = href;
}複製程式碼

在這個重寫後的例子中,使用 with 語句關聯了 location 物件。這意味著在 with 語句的程式碼塊內部,每個變數首先被認為是一個區域性變數,而如果在區域性環境中找不到該變數的定義,就會查詢 location 物件中是否有同名的屬性。如果發現了同名屬性,則以 location 物件屬性的值作為變數的值。

由於大量使用 with 語句會導致效能下降,同時也會給除錯程式碼造成困難,因此在開發大型應用程式時,不建議使用 with 語句。嚴格模式下不允許使用 with 語句,否則將視為語法錯誤。

debugger 語句

debugger 語句通常什麼也不做。然而,當瀏覽器的除錯工具可用並執行的時候,JavaScript 直譯器將會以調式模式執行。實際上,這條語句用來產生一個斷點(breakpoint),JavaScript 程式碼的執行會停止在斷點的位置,這時可以使用偵錯程式輸出變數的值、檢查呼叫棧等。例如:

function f(o) {
    if (o === undefined) {
        debugger;  // 程式會停止在該位置
    }
    // 函式的其他部分
}複製程式碼

use strict 語句

請參見「語法」-「嚴格模式」

關卡

// 挑戰一
var k;
for(i=0, j=0; i<10, j<6; i++, j++){
    k = i + j;
}
console.log(k);  // ???複製程式碼
// 挑戰二
var nums = [12,32,54,56,78,89];
for(var n in nums){
    console.log(n);  // ???
}複製程式碼
// 挑戰三
function showCase(value) {
    switch (value) {
        case 'A':
            console.log('Case A');
            break;
        case 'B':
            console.log('Case B');
            break;
        case undefined:
            console.log('undefined');
            break;
        default:
            console.log('Do not know!');
    }
}
showCase(new String('A'));   // ???複製程式碼
// 挑戰四
function showCase(value) {
    switch (value) {
        case 'A':
            console.log('Case A');
        case 'B':
            console.log('Case B');
            break;
        case undefined:
            console.log('undefined');
            break;
        default:
            console.log('Do not know!');
    }
}
showCase(String('A'));   // ???複製程式碼
// 挑戰五
var i = 0;
for (;;) {
    if (i = 2) {
        continue;
    }
    if (i = 20) {
        break;
    }
    i++;
}
console.log(i);  // ???複製程式碼

更多

關注微信公眾號「劼哥舍」回覆「答案」,獲取關卡詳解。
關注 github.com/stone0090/j…,獲取最新動態。

相關文章