# 語句
表達式在 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` | 擴展作用域鏈 |
## [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#條件語句)條件語句
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#if-else-語句)`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."); // 代碼塊中的語句
}
~~~
業界普遍推崇的最佳實踐是始終使用代碼塊,即使要執行的只有一行代碼。因為這樣可以消除人們的誤解,否則可能讓人分不清在不同條件下要執行哪些語句。
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#switch-語句)`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`?的匹配并不會做任何類型轉換。
## [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#循環)循環
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#while-語句)`while`?語句
`while`?語句屬于前測試循環語句,也就是說,在循環體內的代碼被執行之前,就會對出口條件求值。因引,循環體內的代碼有可能永遠不會被執行。以下是?`while`?語句的語法:
~~~
while(expression) statement
~~~
下面是一個示例:
~~~
var i = 0;
while (i < 10) {
i += 2;
}
~~~
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#do-while-語句)`do-while`?語句
`do-while`?語句是一種后測試循環語句,即只有在循環體中的代碼執行之后,才會測試出口條件。換句話說,在對條件表達式求值之前,循環體內的代碼至少會被執行一次。以下是?`do-while`?語句的語法:
~~~
do {
statement
} while (expression);
~~~
下面是一個示例:
~~~
var i = 0;
do {
i += 2;
} while (i < 10);
~~~
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#for-語句)`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();
}
~~~
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#for-in-語句)`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`?循環輸出的屬性名的順序是不可預測的。具體來講,所有屬性都會被返回一次,但返回的先后次序可能會因瀏覽器而異。
如果表示要迭代的對象的變量值為?`null`?或?`undefined`,`for-in`?語句會拋出錯誤。雖然 ECMAScript 5更正了這一行為;對這種情況不再拋出錯誤,而只是不執行循環體。為了保證最大限度的兼容性,建議在使用?`for-in`?循環之前,先檢測確認該對象的值不是?`null`?或?`undefined`。
## [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#跳轉)跳轉
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#label-語句)`label`?語句
使用?`label`?語句可以在代碼中添加標簽,以便將來使用。以下是?`label`?語句的語法:
~~~
label: statement
~~~
下面是一個示例:
~~~
start: for (var i=0; i < count; i++) {
console.log(i);
}
~~~
這個例子中定義的?`start`?標簽可以在將來由?`break`?或?`continue`?語句引用。加標簽的語句一般都要與?`for`?語句等循環語句配合使用。
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#break-和-continue-語句)`break`?和?`continue`?語句
`break`?和?`continue`?語句用于在循環中精確地控制代碼的執行。其中,`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`?語句退出循環。另一方面,變量?`num`?從?`0`?開始,用于記錄循環執行的次數。在執行`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`?語句導致它少遞增了一次。
`break`?和?`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) {
break outermost;
}
num++;
}
}
console.log(num); // 55
~~~
在這個例子中,`outermost`?標簽表示外部的?`for`?語句。如果每個循環正常執行?`10`?次,則?`num++`?語句就會正常執行?`100`次。換句話說,如果兩個循環都自然結束,`num`?的值應該是?`100`。但內部循環中的?`break`?語句帶了一個參數:要返回到的標簽。添加這個標簽的結果將導致?`break`?語句不僅會退出內部的?`for`?語句(即使用變量?`j`?的循環),而且也會退出外部的`for`?語句(即使用變量?`i`?的循環)。為此,當變量?`i`?和?`j`?都等于?`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`?語句會強制繼續執行循環,退出內部循環,執行外部循環。當?`j`?是?`5`?時,`continue`?語句執行,而這也就意味著內部循環少執行了?`5`?次,因此?`num`?的結果是?`95`。
雖然聯用?`break`、`continue`?和?`label`?語句能夠執行復雜的操作,但如果使用過度,也會給調試帶來麻煩。在此,我們建議如果使用?`label`?語句,一定要使用描述性的標簽,同時不要嵌套過多的循環。
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#return-語句)`return`?語句
`return`?語句的作用是指定函數調用后的返回值。`return`?語句的語法如下:
~~~
return [expression];
~~~
下面是一個示例:
~~~
function square(x) { return x*x; } // 一個包含 return 語句的函數
square(2); // 調用結果為 4
~~~
`return`?語句只能在函數體內出現,如果不是的話會報語法錯誤。當執行到?`return`?語句的時候,函數終止執行,并返回`expression`?的值給調用程序。如果沒有?`return`?語句,則函數調用僅依次執行函數體內的每一條語句直到函數結束,最后返回調用程序。這種情況下,調用表達式的結果是?`undefined`。`return`?語句經常作為函數內的最后一條語句出現,但并不是說要一定放在函數最后。`return`?語句可以單獨使用而不必帶有?`expression`,這樣的話函數也會向調用程序返回?`undefined`。
由于 JavaScript 可以自動插入分號,因此在?`return`?關鍵字和它后面的表達式之間不能有換行。
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#throw-語句)`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 將把異常當成程序錯誤來處理,并報告給用戶。
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#try-語句)`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`?塊內的邏輯總是會執行。盡管?`catch`?和?`finally`?都是可選的,但?`try`?從句需要至少二者之一與之組成完整的語句。`try`、`catch`?和?`finally`?語句塊都需要使用花括號括起來,這里的花括號是必需的,即使從句中只有一條語句也不能省略花括號。
下面的代碼詳細的說明了?`try-catch-finally`?的使用目的:
~~~
try {
// 通常來講,這里的代碼會從頭執行到尾而不會產生任何問題,
// 但有時會拋出一個異常,要么是由 throw 語句直接拋出異常,
// 要么是通過調用一個方法間接拋出異常
}
catch(e) {
// 當且僅當 try 語句塊拋出了異常,才會執行這里的代碼
// 這里可以通過局部變量 e 來獲得對 Error 對象或者拋出的其他值的引用
// 這里的代碼塊可以基于某種原因處理這個異常,也可以忽略這個異常,
// 還可以通過 throw 語句重新拋出異常
}
finally {
// 不管 try 語句塊是否拋出了異常,這里的邏輯總是會執行,終止 try 語句塊的方式有:
// 1)正常終止,執行完語句塊的最后一條語句
// 2)通過 break、continue 或 return 語句終止
// 3)拋出一個異常,異常被 catch 從句捕獲
// 4)拋出一個異常,異常未被捕獲,繼續向上傳播
}
~~~
## [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#其他)其他
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#with-語句)`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`?語句,否則將視為語法錯誤。
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#debugger-語句)`debugger`?語句
`debugger`?語句通常什么也不做。然而,當瀏覽器的調試工具可用并運行的時候,JavaScript 解釋器將會以調式模式運行。實際上,這條語句用來產生一個斷點(breakpoint),JavaScript 代碼的執行會停止在斷點的位置,這時可以使用調試器輸出變量的值、檢查調用棧等。例如:
~~~
function f(o) {
if (o === undefined) {
debugger; // 程序會停止在該位置
}
// 函數的其他部分
}
~~~
### [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#use-strict-語句)`use strict`?語句
> 請參見[「語法」-「嚴格模式」](https://github.com/stone0090/javascript-lessons/tree/master/1.3-Syntax#%E4%B8%A5%E6%A0%BC%E6%A8%A1%E5%BC%8F)。
## [](https://github.com/stone0090/javascript-lessons/tree/master/1.6-Statements#關卡)關卡
~~~
// 挑戰一
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); // ???
~~~