ECMA-262 规定了一组语句(也成为流控制语句)。从本质上看,语句定义了 ES 中的主要语法,语句通常使用一个或多个关键字来完成给定任务。语句可以很简单,例如通知函数退出;也可以比较复杂,例如指定重复执行某个命令的次数。
1.if 语句
大多数编程语言中最为常用的一个语句就是 if 语句。以下是 if 语句的语法:
1 | if (condition) statement1 else statement2 |
其中的 condition (条件)可以是任意表达式;而且对这个表达式求值的结果不一定是布尔值。ES 会自动调用 Boolean()
转换函数将这个表达式的结果转换为一个布尔值。如果对 condition 求值的结果是 true
,则执行 statement1
;反之,则执行 statement2
。而且这两个语句既可以是一行代码,也可以是一个代码块。
1 | if (i > 25) |
建议始终使用代码块,即时要执行的只有一行代码。因为这样可以消除误解,否则可能让人分不清在不同的条件下要执行哪些语句。
另外,也可以像下面这样把整个 if 语句写在一行代码中:
1 | if (condition1) statement1 else if (condition2) statement2 else statement3 |
但推荐的做法是像下面这样:
1 | if (i > 25) { |
2.do-while 语句
do-while 语句是一种后测试循环语句,即只有在循环体中的代码执行之后,才会测试出口条件。换句话说,在对条件表达式求值之前,循环体内的代码至少会被执行一次。以下是 do-while 语句的语法:
1 | do { |
下面是一个示例:
1 | var i = 0; |
在这个例子中,只要变量 i
的值小于 10
,循环就会一直继续下去。而且变量 i
的值最初为 0
,每次循环都会递增 2
。
像 do-while 这种后测试循环语句最常用于循环体中的代码至少要被执行一次的情形。
3.while 语句
while 语句属于前测试循环语句,也就是说,在循环体内的代码被执行之前,就会对出口条件求值。因此,循环体内的代码有可能永远不会被执行。以下是 while 语句的语法:
1 | while(expression) statement |
下面是一个示例:
1 | var i = 0; |
在这个例子中,变量 i
开始时的值为 0
,每次循环都会递增 2
。而只要 i
的值小于 10
,循环就会继续下去。
4.for 语句
for 语句也是一种前测试循环语句,但它具有在执行循环之前初始化变量和定义循环后要执行的代码的能力。以下是 for 语句的语法:
1 | for (initialization; expression; post-loop-expression) statement |
下面是一个示例:
1 | var count = 10; |
以上代码定义了变量 i
的初始值为 0
。只有当条件表达式 i < count
返回 true
的情况下才会进入 for
循环,因此也有可能不会执行循环体中的代码。如果执行了循环体中的代码,则一定会对循环后的表达式 i++
求值,即递增 i
的值。这个 for 循环语句与下面的 while 语句的功能相同:
1 | var count = 10; |
使用 while 循环做不到的,使用 for 循环同样也做不到。也就是说,for 循环只是把与循环有关的代码集中在了一个位置。
有必要指出的是,在 for 循环的变量初始化表达式中,也可以不使用 var
关键字。该变量的初始化可以在外部执行,例如:
1 | var count = 10; |
以上代码与在循环初始化表达式中声明变量的效果是一样的。由于 ES5 中不存在块级作用域,因此在循环内部定义的变量也可以在外部访问到(ES6 中增加了块级作用域)。
1 | var count = 10; |
在这个例子中,会有一个警告框显示循环完成后变量 i
的值,这个值是 10
。这是因为,即使 i
是在循环内部定义的一个变量,但在循环外部仍然可以访问到它。
此外,for 语句中的初始化表达式、控制表达式和循环后表达式都是可选的。将这三个表达式全部省略,就会创建一个无限循环,例如:
1 | for(;;) { // 无限循环 |
而只给出控制表达式实际上就把 for 循环转换成了 while 循环,例如:
1 | var count = 10; |
由于 for 语句存在极大的灵活性,因此它也是 ES 中最常用的一个语句。
5.for-in 语句
for-in 语句是一种精准迭代语句,可以用来枚举对象的属性。以下是 for-in 语句的语法:
1 | for (property in expression) statement |
下面是一个示例:
1 | for (var propName in window) { |
在这个例子中,我们使用 for-in 循环来显示了 BOM 中 window
对象的所有属性。每次执行循环时,都会将 window
对象中存在的一个属性名赋值给变量 propName
。这个过程会一直持续到对象中的所有属性都被枚举一遍为止。
ES 对象的属性没有顺序。因此,通过 for-in 循环输出的属性名的顺序是不可预测的。具体来讲,所有属性都会被返回一次,但返回的先后次序可能会因浏览器而异。
但是,如果表示要迭代的对象的变量值为 null
或 undefined
,for-in 语句会抛出错误。ES5 更正了这一行为:对这种情况不再抛出错误,而只是不执行循环体。为了保证最大限度的兼容性,建议在使用 for-in 循环之前,先检测确认该对象的值不是 null 或 undefined。
Safari3 以前版本的 for-in 语句中存在一个 bug,该 bug 会导致某些属性被返回两次。
6.label 语句
使用 label 语句可以在代码中添加标签,以便将来使用。以下是 label 语句的语法:
1 | label: statement |
下面是一个示例:
1 | start: for (var i = 0; i < count; i++) { |
这个例子中定义的 start
标签可以在将来由 break
或 continue
语句引用。加标签的语句一般都要与 for 语句等循环语句配合使用。
7.break 和 continue 语句
break 和 continue 语句用于在循环中精确地控制代码的执行。其中,break 语句会立即退出循环,强制继续执行循环后面的语句。而 continue 语句虽然也是立即退出循环,但退出循环后会从循环的顶部继续执行。例如:
1 | var num = 0; |
这个例子中的 for 循环会将变量 i
由 1
递增至 10
。在循环体内,有一个 if 语句检查 i
的值是否可以被 5
整除。如果是,则执行 break 语句退出循环。另一方面,变量 num
从 0
开始,用于记录循环执行的次数。在执行 break 语句之后,要执行的下一行代码是 alert()
函数,结果显示 4
。也就是说,在变量 i
等于 5
时,循环总共执行了 4
次;而 break 语句的执行,导致了循环在 num
再次递增之前就退出了。如果在这里把 break 替换为 continue 的话,则可以看到另一种结果:
1 | var num = 0; |
例子的结果显示 8
,也就是循环总共执行了 8
次。当变量 i
等于 5
时,循环会在 num
再次递增之前退出,但接下来执行的是下一次循环,即 i
的值等于 6
的循环。于是,循环又继续执行,直到自然结束。
break 和 continue 语句都可以与 label 语句联合使用,从而返回代码中特定的位置。这种联合使用的情况多发生在循环嵌套的情况下,如下面的例子所示:
1 | var num = 0; |
在这个例子中,outermost
标签表示外部的 for 语句。如果每个循环正常执行 10
次,则 num++
语句就会正常执行 100
次。换句话说,如果两个循环都自然结束,num
的值应该是 100
。但内部循环中的 break 语句带了一个参数:要返回到的标签。添加这个标签的结果将导致 break 语句不仅会退出内部的 for 语句,而且也会退出外部的 for 语句。为此,当变量 i
和 j
都等于 5
时,num
的值正好是 55
。同样,continue 语句也可以像这样与 label 语句联用,如下面的例子所示:
1 | var num = 0; |
在这种情况下,continue 语句会强制继续执行循环 —— 退出内部循环,执行外部循环。当 j
是 5
时,continue 语句执行,而这页就意味着内部循环少执行了 5
次,因此 num
的结果是 95
。
8.with 语句
with 语句的作用是将代码的作用域设置到一个特定的对象中。with 语句的语法如下:
1 | with (expression) statement; |
定义 with 语句的目的主要是为了简化多次编写同一个对象的工作,如下面的例子所示:
1 | var qs = location.search.substring(1); |
上面几行代码都包含 location
对象。如果使用 with 语句,可以把上面的代码改写成如下所示:
1 | with (location) { |
在这个重写后的例子中,使用 with 语句关联了 location
对象。这意味着在 with 语句的代码块内部,每个变量首先被认为是一个局部变量,而如果在局部环境中找不到该变量的定义,就会查询 location
对象中是否有同名的属性。如果发现了同名属性,则以 location
对象属性的值作为变量的值。
注意:严格模式下不允许使用 with 语句,否则视为语法错误;另外由于大量使用 with 语句会导致性能下降,同时会给调试代码造成困难,因此在开发大型应用程序时,不建议使用 with 语句。
9.switch 语句
switch 语句与 if 语句的关系最为密切,而且也是在其他语言中普遍使用的一种流控制语句。ES 中 switch 语句的语法与其他基于 C 的语言非常接近,如下所示:
1 | switch (expression) { |
switch 语句中的每一种情形(case
) 的含义是:“如果表达式等于这个值(value
),则执行后面的语句(statement
)”。而 break
关键字会导致代码执行流跳出 switch 语句。如果省略 break
关键字,就会导致执行完当前 case
后,继续执行下一个 case
。最后的 default
关键字则用于在表达式不匹配前面任何一种情形的时候,执行机动代码(因此,也相当于一个 else
语句)。
从根本上讲,switch 语句就是为了让开发人员免于编写像下面这样的代码:
1 | if (i == 25) { |
而与此等价的 switch 语句如下所示:
1 | switch (i) { |
通过为每个 case
后面都添加一个 break 语句,就可以避免同时执行多个 case
代码的情况。假如确实需要混合几种情形,不要忘了在代码中添加注释,说明你是有意省略了 break 关键字,如下所示:
1 | switch (i) { |
虽然 ES 中的 switch 语句借鉴自其他语言,但这个语句也有自己的特色。首先,可以在 switch 语句中使用任何数据类型(在很多其他语言中只能使用数值),无论是字符串,还是对象都没有问题。其次,每个 case 的值不一定是常亮,可以是变量,甚至是表达式。请看下面这个例子:
1 | switch ("hello world") { |
在这个例子中,switch 语句使用的就是字符串。其中,第一种情形实际上是一个对字符串拼接操作求值的表达式。由于这个字符串拼接表达式的结果与 switch 的参数相等,因此结果就会显示 "Greeting was found."
。而且,使用表达式作为 case
值还可以实现下列操作:
1 | var num = 25; |
这个例子首先在 switch 语句外面声明了变量 num
。而之所以给 switch 语句传递表达式 true
,是因为每个 case
值都可以返回一个布尔值。这样,每个 case
按照顺序被求值,直到找到匹配的值或者遇到 default
语句为止。
switch 语句在比较值时使用的是全等操作符,因此不会发生类型转换。