# 语句

表达式在 javascript 中是短语，那么语句就是 javascript 整句或者命令。javascript 语句是以分号结束。表达式计算出一个值，但语句用来执行以使某件事发生。

“使某件事发生”的一个方法是计算带有副作用的表达式。诸如赋值和函数调用这些有副作用的表达式，是可以作为单独的语句的，这种把表达式当做语句的用法也称做表达式语句。类似的语句还有声明语句，声明语句来声明新变量或定义新函数。

javascript 中有很多语句和控制结构来改变语句的默认执行顺序:条件语句，循环语句，跳转语句。

### 复合和空语句

javascript 中可以将多条语句联合在一起，形成一条复合语句。只须用花括号括起来就行。例如：

```javascript
{
  x = Math.pI
  cx = Math.cos(x)
  console.log('cos(Π)=' + cx)
}
```

当希望多条语句被当做一条语句使用时，使用复合语句来代替。空语句则恰好相反，它允许包含 `0` 条语句的语句。例如：

```
;
```

实践证明，当创建一个具有空循环体的循环时，空语句有时候是很有用的，例如下面的 `for` 循环：

```javascript
//初始化一个数组a
for (i = 0; i < a.length; a[i++] = 0);
```

此循环中，所有的操作都在表达式 `a[i++]=0` 中完成，这里不需要任何循环体。然而 javascript 需要循环体中至少包含一条语句，因此，这里只使用了一个单独的分号来表示一条空语句。

### 声明语句

var 和 function 都是声明语句，他们声明或定义变量或函数。

#### function

函数声明语句的语法如下：

```javascript
function 方法名(参数) {
  语句块
}
```

函数声明语句中的函数名是一个变量名，变量指向函数对象。和通过 `var` 声明变量一样，函数定义语句中的函数被显示地“提前”到了脚本或函数的顶部。因此它们在整个脚本和函数内部都是可见的。即函数名称和函数体均提前：脚本中的所有函数和函数中所有嵌套的函数都会在当前上下文中其他代码之前声明，也就是说，可以在声明一个 javascript 函数之前调用它。

### 条件语句

#### switch

如果在函数中使用 `switch` 语句，可以使用 `return` 来代替 `break`，`return` 和 `break` 都用于终止 `switch` 语句，也会防止一个 `case` 语句块执行完成后继续执行下一个 `case` 语句块。例如：

```javascript
function convert(x) {
  switch (typeof x) {
    case 'number':
      return x.toString(16)
    case 'string':
      return '""' + x + '""'
    default:
      return String(x)
  }
}
```

对每个 `case` 的匹配操作实际上是`“===”`比较，不会做任何类型的转换。并不是所有的 `case` 表达式都能执行到，所以比较安全的做法是在 `case` 表达式中使用常量表达式。

### 循环

#### while

`while` 语句也是一个基本循环语句，其语法如下：

```javascript
while (expression) statement
```

#### do/while

`do/while` 循环和 `while` 循环非常相似，只不过它是在循环尾部而不是顶部检测循环表达式，这意味着循环体至少会执行一次，和 `while` 循环不同，`do` 循环是用分号结尾的，其语法如下：

```javascript
do statement
while (expression)
```

#### for

在 `for` 循环中，数字变量不是必需的。下面这段代码使用 `for` 循环来遍历链表数据结构，并返回最后一个对象：

```javascript
function tail(0) {
    for(; o.next; o = o.next) /* empty */ ;
    return o;
}
```

`for` 循环中三个表达式中的任何一个都可以忽略，但两个分号必不可少。

#### for/in

`for/in` 语句也使用 `for` 关键字，但它是和常规的 `for` 循环完全不同的一类循环。`for/in` 循环语句的语法如下：

```javascript
for (variable in object) statement
```

`for/in` 循环用来遍历对象属性成员：

```javascript
for (var p in o) console.log(o[p])
```

在执行语句过程中，javascript 解释器首先计算 `object` 表达式。如果表达式为 `null` 或者 `undefined`，javascript 解释器将会跳过循环并执行后续代码。如果表达式等于一个原始值，这个原始值将会转换为与之对相应的包装对象。否则，`expression` 本身已经是对象了。javascript 会依次枚举对象的属性来执行循环。然而在每次循环之前，javascript 都会先计算 `variable` 表达式的值，并将属性名(一个字符串)赋值给它。

只要 `for/in` 循环中 `variable` 的值可以当做赋值表达式的左值，它可以是任意表达式。每次循环都会计算这个表达式，例如：

```javascript
var o = { x: 1, y: 2, z: 3 }
var a = [],
  i = 0
for (a[i++] in o /* empty */);
```

javascript 数组是一种特殊的对象，`for/in`循环可以像枚举对象属性一样枚举数组索引。例如，在上面代码之后加上这段代码就可以枚举数组的索引 `0,、1、2`：

```javascript
for (i in a) console.log(i)
```

`for/in` 循环并不会遍历对象的所有属性，只有“可枚举”的属性才会遍历到。如果 `for/in` 的循环体删除了还未枚举的熟悉 `1`，那么这个属性将不会再枚举到。如果循环体定义看对象的新属性，这些属性通常也不会枚举到，不过，javascript 的有些实现是可以枚举那些在循环体中增加的继承属性的。

ECMAScript 规范并没有指定 `for/in` 循环按照何种顺序来枚举对象的属性。但实际上，主流浏览器厂商的 javascript 实现是按照属性定义的先后顺序来枚举简单对象的属性，在下列情况下，枚举的顺序取决于具体的实现：

1. 对象继承了可枚举属性；
2. 对象具有整数数组索引的属性；
3. 使用 delete 删除了对象已有的属性；
4. 使用 Object.defineproperty()或类似的方法改变了对象的属性。

### 跳转

#### 标签语句

语句添加标签语法：

```javascript
identifier: statement
```

在循环和条件判断语句中，通过给循环定义一个标签名，可以在循环体内部使用 `break` 和 `continue` 来退出循环或者直接跳转到下一个循环的开始。`break` 和 `continue` 是 javascript 中*唯一*可以使用语句标签的语句。下面为一个例子：

```javascript
mainloop: while(token ! null){
    // 忽略这里的代码...
    continue mainloop;  // 跳转到下一个循环
    // 忽略这里的代码
}
```

标签的命名空间和变量或者函数的命名空间是不同的，语句标签只有在它所起作用的语句(或者子句中)内是有定义的。一个语句标签不能和它内部的语句标签重名，但在两个代码段不互相嵌套的情况下是可以出现同名的语句标签的。带有标签的语句还可以带有标签，也就是说，任何语句可以多个标签。

#### break 语句

单独使用 `break` 语句的作用是立即退出最内层的循环或者 `switch` 语句。javascript 中允许 `break` 关键字后面跟随一个语句标签：

```javascript
break labelname;
```

当 `break` 和标签一块使用时，程序将跳转到这个标签所标识的语句的结束，或者直接终止这个闭合语句块的执行。

当你希望通过 `break` 来跳转出非就近的循环体或者 `switch` 语句时，就会用到带标签的 `break` 语句。例如：

```javascript
var matrix = getData();  // 从某处得到一个二维数组
// 将矩阵中所有元素进行求和
var sum = 0, success = false;
//从标签名开始，以便在报错是退出程序
compute sum: if (matrix) {
    for(var x = 0; x < matrix.length; x++){
      var row = matrix[x];
      if(!row) break compute_sum;
         for(var y = 0;y < row.length; y++) {
             var cell = row[y];
             if (isNaN(cell)) break compute_sum;
             sum += cell;
         }
     }
     success = true;
}
// break语句跳转至此
// 如果success == false到达这里，说明矩阵有错
// 否则求和
```

不管 `break` 语句带不带标签，它的控制权都无法越过函数的边界。比如，对于一条带标签的函数定义语句来说，不能从函数内部通过这个标签来跳转到函数外。

#### continue 语句

`continue` 可以带有标签，并且不管其带不带标签，都只能在循环体内使用。在其他地方使用将会报语法错误。

当执行到 `continue` 语句的时候，当前的循环逻辑就终止了，随即执行下一次循环，在不同类型循环中，其行为有所区别：

1. 在 while 循环中，在循环开始出指定的 expression 会重复检测，如果 true 则从头开始执行。
2. 在 do/while 循环中，程序的执行直接跳转到循环结尾处，这时会重新判断循环条件，之后才会继续下一次循环。
3. 在 for 循环中，首先会计算自增表达式，然后再次检测 test 表达式，用以判断是否执行循环体。
4. 在 for/in 循环中，循环开始遍历下一个属性名，这个属性名赋给了指定的变量。

由于 `continue` 在 `while` 和 `for` 中表现不同，因此使用 `while` 循环不可能完美的模拟等价的 `for` 循环。

#### throw 语句

所谓异常是当发生了某种异常情况或者错误时产生的一个信号。其语法如下：

```javascript
throw expression
```

#### try/catch/finally 语句

`try/catch/finally` 语句是 javascript 的异常处理机制。其中 `try` 从句定义了需要处理的异常所在的代码块。`catch` 从句跟随在 `try` 从句之后，当 `try` 块内某处发生了异常是，调用 `catch`的代码逻辑。`catch` 从句后跟随 `finally` 块，后者中放置清理代码，不管 `try` 块中是否产生异常，`finally` 块内的逻辑总是会执行。尽管 `catch` 和 `finally` 都是可选的，但 `try` 从句需要至少二者之一与之组成完整的语句。`try`、`catch` 和 `finally` 语句块都需要使用花括号括起来，这里的花括号是必需的，即使只有一条语句。

下面代码说明 `try/catch/finally` 的语法和使用目的：

```javascript
try {
  //通常来讲，这里的代码会从头执行到尾而不会产生任何问题，
  //但有时会抛出一个异常，要么是由throw语句直接抛出异常，
  //要么是通过调用一个方法间接抛出异常
} catch (e) {
  //当且仅当try语句块抛出了异常，才会执行这里的代码
  //这里可以通过局部变量e来或者对Error对象或者抛出的其他值的引用
} finally {
  //这里逻辑总会执行终止try语句块的方式有：
  // 1) 正常终止，执行完结语句块的最后一条语句
  // 2) 通过break、continue或return语句终止
  // 3) 抛出一个异常，异常被catch从句捕获
  // 4) 抛出一个异常，异常未被捕获，继续向上传播
}
```

下面的例子更为贴近实际，这里使用了前面提到 `factorial()`方法，并使用客户端 javascript 方法 `prompt()`和 `alert()`来输入和输出：

```javascript
try {
  // 要求用户输入一个数字
  var n = Number(prompt('请输入一个正整数', ''))
  // 假设输入时合法的，计算这个数的阶乘
  var f = factorial(n)
  // 显示结果
  alert(n + '! = ' + f)
} catch (ex) {
  //如果输入不合法，将执行这里的逻辑
  alert(ex) // 告诉用户产生了什么错误
}
```

`finally` 可以在 `try` 执行后用于清理工作。如果 `finally` 块使用了 `return`、`continue`、`break` 和 `throw` 语句使程序发生跳转，或者通过调用了抛出异常的方法改变了程序执行流程，不管这个跳转时程序挂起还是继续执行，解释器都会将其忽略。

```javascript
var foo = function(){
    try{
    //抛出一个异常
    }
    finally {
        return 1;//未处理异常直接返回，这里将正常返回
    };
foo();
```

这里 `foo()`会正常返回 `1`；

### 其他语句类型

#### with 语句

`with` 语句用于临时扩展作用域链，语法如下：

```javascript
with (object) statement
```

在严格模式中是禁止使用 `with` 语句的，并且在非严格模式里也是不推荐的。使用 `with` 的语句非常难于优化，运行的速度慢。例如：

```javascript
with (document.forms[0]) {
  //这里直接访问表单元素
  //即省去了前缀document.forms[0]
}
```

只有在查找标识符的时候才会用到作用域链，创建新的变量的时候不使用它，例如：

```javascript
with (o) x = 1
```

如果对象 `o` 有一个属性 `x`，那么这行代码给这个属性赋值为 `1`.但如果 `o` 中没有定义属性 `x`，这段代码和不使用 `with` 语句的代码 `x = 1` 是一模一样的。它给一个局部变量或者去全局变量 `x` 赋值，或者创建全局对象的一个新的属性。

#### debugger 语句

`debugger` 通常什么也不做。当调试程序可用并运行时，javascript 解释器将会以调试模式 `1` 运行。使用这条语句将产生一个断点，javascript 代码的执行会停止在断点的位置。例如：

```javascript
function f(o) {
  if (o === undefined) debugger; //这一行代码只是用于临时调试
  ...                            //函数的其他部分
}
```

#### “use strict”

严格模式和非严格之间的区别(前三条尤为重要)：

1. 在严格模式中禁止使用 `with` 语句
2. 在严格模式中，所有的变量都要先声明。
3. 在严格模式中，调用的函数(不是方法)中的一个 `this` 值是 `undefined`(在非严格模式中，调用函数 `this` 值总是全局对象)。可以利用这种特性来判断 javascript 实现是否支持严格模式：

   var hasStrictMode = (function() { "use strict"; return this===undefined});
4. 同样，在严格模式中，当通过 `call()`或 `apply()`来调用函数时，其中的 `this` 值就是通过 `call()`或 `apply()`传入的第一个参数(在非严格模式中，`null` 和 `undefined` 值被全局对象和转换为对象的非对象值所代替)。
5. 在严格模式中，给只读属性赋值和不可拓展的对象创建新成员都将抛出一个类型错误异常(在非严格模式中，这些操作只是简单的操作失败，不会报错)。
6. 在严格模式中，传入 `eval()`的代码不能在调用程序所在的上下文变量或定义函数，变量和函数的定义是在 `eval()`创建的新作用域中，这个作用域在 `eval()`返回时就弃用了。
7. 在严格模式中，函数里的 `arguments` 对象拥有传入函数值的副本。在非严格模式下，`arguments` 对象的数组元素和函数都是指向同一个值的引用。
8. 在严格模式中，当 `delete` 运算符后跟随非法的标识符时。将会抛出一个语法错误异常。
9. 在严格模式中，试图删除一个不可配置的属性将抛出一个类型错误异常。
10. 在严格模式中，在一个对象直接量中定义两个或多个同名属性将产生一个语法错误。
11. 在严格模式中是不允许使用八进制整数直接量的。
12. 在严格模式中，标识符 `eval` 和 `arguments` 当做关键，他们的值是不能更改的。不能给这些标识符赋值，也不能把它们声明为变量。用作函数名、用做函数名、用做函数参数或用做 `catch` 块的标识符
13. 在严格模式中限制了对调用栈的检测的能力，在严格模式下的函数，`arguments.caller` 和 `arguments.callee` 都会抛出一个类型错误异常。
