JavaScript 系列四:流程控制
前言
在开始学习之前,我们想要告诉您的是,本文章是对JavaScript
语言知识中 "流程控制" 部分的总结,如果您已掌握下面知识事项,则可跳过此环节直接进入题目练习
- if 语句
- while、do-while 语句
- for 语句、for-in 语句、for-of 语句
- break 语句和 continue 语句
- switch 语句
- try/catch 语句
汇总总结
ECMA-262
描述了一些语句(也称为流控制语句),而 ECMAScript
中的大部分语法都体现在语句中。 语句通常使用一或多个关键字完成既定的任务。语句可以简单,也可以复杂。简单的如告诉函数退出,复杂的如列出一堆要重复执行的指令。
if 语句
if
语句是使用最频繁的语句之一,语法如下:
if (condition) {
statement1
} else {
statement2
}
这里的条件(condition
)可以是任何表达式,并且求值结果不一定是布尔值。ECMAScript
会自动调用 Boolean()
函数将这个表达式的值转换为布尔值。如果条件求值为 true
,则执行语句 statement1
;如果条件求值为 false
,则执行语句 statement2
。这里的语句可能是一行代码,也可能是一个代码块(即包含在一对花括号中的多行代码)如下列代码:
if (xhs > 1) {
console.log('xhs满足大于1!')
} else {
console.log('xhs不满足大于1!')
}
上面代码当 xhs
满足大于 1
时,输出 xhs满足大于 1!
,当 xhs
不满足大于 1
时,输出 xhs 不满足大于 1!
。
可以像这样连续使用多个 if
语句:
if (condition1) {
statement1
} else if (condition2) {
statement2
} else {
statement3
}
下面是一个例子:
if (xhs > 25) {
console.log('xhs满足大于25')
} else if (xhs < 0) {
console.log('xhs满足小于0')
} else {
console.log('xhs在0到25之间')
}
do-while 语句
do-while
语句是一种后测试循环语句,即循环体中的代码执行后才会对退出条件进行求值。换句话说,循环体内的代码至少执行一次。do-while
的语法如下:
do {
statement
} while (expression)
下面是一个例子:
let xhs = 0
do {
xhs += 2
} while (xhs < 10)
在上面的例子中,变量 xhs
先加了 2
,然后用 2
去与10
做比较,如果小于 10
,循环就会重复执行,直到 xhs >= 10
。
注意 后测试循环经常用于这种情形:循环体内代码在退出前至少要执行一次。
while 语句
while
语句是一种先测试循环语句,即先检测退出条件,再执行循环体内的代码。因此,while
循环体内的代码有可能不会执行。下面是 while
循环的语法:
while (expression) {
statement
}
这是一个例子:
let i = 0
while (i < 10) {
i += 2
}
在这个例子中,变量 xhs
从 0
开始,每次循环递增 2
。只要 xhs
小于 10
,循环就会继续。
for 语句
for
语句也是先测试语句,只不过增加了进入循环之前的初始化代码(initialization
),以及循环执行后要执行的表达式(loop-expression
),语法如下:
for (initialization; expression; loop - expression) {
statement
}
下面是一个用例:
let xhsLength = 10
for (let xhs = 0; xhs < xhsLength; xhs++) {
console.log(xhs)
}
以上代码在循环开始前定义了变量 xhs
的初始值为 0
。然后求值条件表达式,如果求值结果为 true (xhs < xhsLength)
,则执行循环体。因此循环体也可能不会被执行。如果循环体被执行了,则循环后表达式也会执行,以便递增变量 xhs
。for
循环跟下面的 while
循环是一样的:
let xhsLength = 10
let xhs = 0
while (xhs < xhsLength) {
console.log(xhs)
xhs++
}
无法通过 while
循环实现的逻辑,同样也无法使用 for
循环实现。因此 for
循环只是将循环相关的代码封装在了一起而已。
在 for
循环的初始化代码中,其实是可以不使用变量声明关键字的。不过,初始化定义的迭代器变 量在循环执行完成后几乎不可能再用到了。因此,最清晰的写法是使用 let
声明迭代器变量,这样就可以将这个变量的作用域限定在循环中。
初始化、条件表达式和循环后表达式都不是必需的。因此,下面这种写法可以创建一个无穷循环
for (;;) {
// 无穷循环
doSomething()
}
如果只包含条件表达式,那么 for
循环实际上就变成了 while
循环
let xhsLength = 10
let xhs = 0
for (; xhs < xhsLength; ) {
console.log(xhs)
xhs++
}
这种多功能性使得 for
语句在这门语言中使用非常广泛。
for-in 语句
for-in
语句是一种严格的迭代语句,用于枚举对象中的非符号键属性,语法如下:
for (property in expression) {
statement
}
下面是一个例子:
for (const xhsName in object) {
console.log(xhsName)
}
这个例子使用 for-in
循环显示了对象 object
的所有属性。每次执行循环,都会给变量 xhsName
赋予一个 object
对象的属性作为值,直到 object
的所有属性都被枚举一遍。与 for
循环 一样,这里控制语句中的 const
也不是必需的。但为了确保这个局部变量不被修改,推荐使用 const
。
ECMAScript
中对象的属性是无序的,因此 for-in
语句不能保证返回对象属性的顺序。换句话说,所有可枚举的属性都会返回一次,但返回的顺序可能会因浏览器而异。
如果 for-in
循环要迭代的变量是 null
或undefined
,则不执行循环体。
for-of 语句
for-of
语句是一种严格的迭代语句,用于遍历可迭代对象的元素,语法如下:
也就是说只要原型对象实现了@@iterator
方法,该对象即可被for-of
for (property of expression) {
statement
}
下面是示例:
for (const xhsName of [2, 4, 6, 8]) {
console.log(xhsName) //分别打印2,4,6,8
}
在这个例子中,我们使用 for-of
语句显示了一个包含 4 个元素的数组中的所有元素。循环会一直持续到将所有元素都迭代完。与for
循环一样,这里控制语句中的 const
也不是必需的。但为了确保这个局部变量不被修改,推荐使用 const
。
for-of
循环会按照可迭代对象的 next()
方法产生值的顺序迭代元素。关于可迭代对象,请详见 ES6 系列的 Iterator
篇。
如果尝试迭代的变量不支持迭代,则 for-of
语句会抛出错误。
break 和 continue 语句
break
和 continue
语句为执行循环代码提供了更严格的控制手段。其中,break
语句用于立即退出循环,强制执行循环后的下一条语句。而 continue
语句也用于立即退出循环,但会再次从循环顶部开始执行。下面看一个例子:
for (let xhs = 1; xhs < 10; xhs++) {
if (xhs % 5 == 0) {
break
}
xhsNumber++
}
console.log(xhsNumber) // 4
在上面的代码中,for
循环会将变量 xhs
由 1
递增到 10
。而在循环体内,有一个 if
语句用于检查 xhs
能否被整除(使用取模操作符)。如果是,则执行 break
语句,退出循环。变量 xhsNumber
的初始值为 0
, 表示循环在退出前执行了多少次。当 break
语句执行后,下一行执行的代码是 console.log(num)
, 显示 4
。之所以循环执行了 4 次,是因为当 xhs
等于 5
时,break
语句会导致循环退出,该次循环不会执行递增 xhsNumber
的代码。如果将 break
换成 continue
,则会出现不同的效果:
let xhsNumber = 0
for (let xhs = 1; xhs < 10; xhs++) {
if (xhs % 5 == 0) {
continue
}
xhsNumber++
}
console.log(xhsNumber) // 8
这一次,console.log
显示 8
,即循环被完整执行了 8 次。当 xhs
等于 5
时,循环会在递增 xhsNumber
之前退出,但会执行下一次迭代,此时 xhs
是 6
。然后,循环会一直执行到自然结束,即 xhs
等于 10
。最终 xhsNumber
的值是 8
而不是 9
,是因为 continue
语句导致它少递增了一次。
break
和 continue
都可以与标签语句一起使用,返回代码中特定的位置。这通常是在嵌套循环中,如下面的例子所示:
let xhsNumber = 0
for (let xhs = 1; xhs < 10; xhs++) {
if (xhs % 5 == 0) {
continue
}
xhsNumber++
}
console.log(xhsNumber) // 8
在这个例子中,xhsRookies
标签标识的是第一个 for
语句。正常情况下,每个循环执行 10 次,意味着 xhsNumber++
语句会执行 100 次,而循环结束时 console.log
的结果应该是 100
。但是,break
语句带来了一个变数,即要退出到的标签。添加标签不仅让 break
退出内部循环,也会退出外部循环。当执行到 xhs
和 xhsJJ
都等于 5
时,循环停止执行,此时 xhsNumber
的值是 55
。continue
语句也可以使用标签,如下面的例子所示:
let xhsNumber = 0
xhsRookies: for (let xhs = 0; xhs < 10; xhs++) {
for (let xhsJJ = 0; xhsJJ < 10; xhsJJ++) {
if (xhs == 5 && xhsJJ == 5) {
continue xhsRookies
}
xhsNumber++
}
}
console.log(xhsNumber) // 95
这一次,continue
语句会强制循环继续执行,但不是继续执行内部循环,而是继续执行外部循环。 当 xhs
和 xhsJJ
都等于 5
时,会执行 continue
,跳到外部循环继续执行,从而导致内部循环少执行 5 次,结 果 xhsNumber
等于 95
。
组合使用标签语句和break
、continue
能实现复杂的逻辑,但也容易出错。注意标签要使用描述性强的文本,而嵌套也不要太深。
switch 语句
switch
语句是与 if
语句紧密相关的一种流控制语句,如下所示:
switch (expression) {
case value1:
statement
break
case value2:
statement
break
case value3:
statement
break
case value4:
statement
break
default:
statement
}
这里的每个 case
(条件/分支)相当于:“如果表达式等于后面的值,则执行下面的语句。”break
关键字会导致代码执行跳出 switch
语句。如果没有 break
,则代码会继续匹配下一个条件。default
关键字用于在任何条件都没有满足时指定默认执行的语句(相当于 else
语句)。
有了 switch
语句,开发者就用不着写类似这样的代码了:
if (xhs == 25) {
console.log('25')
} else if (xhs == 35) {
console.log('35')
} else if (xhs == 45) {
console.log('45')
} else {
console.log('xhsRookies')
}
而是可以这样写:
switch (xhs) {
case 25:
console.log('25')
break
case 35:
console.log('35')
break
case 45:
console.log('45')
break
default:
console.log('xhsRookies')
}
为避免不必要的条件判断,最好给每个条件后面都加上 break
语句。如果确实需要连续匹配几个条件,那么推荐写个注释表明是故意忽略了 break
,如下所示:
switch (xhs) {
case 25:
console.log('25')
/*跳过*/
case 35:
console.log('25 or 35')
break
case 45:
console.log('45')
break
default:
console.log('xhsRookies')
}
ECMAScript
为 switch
赋予了一些独有的特性。首先,switch
语句可以用于所有数据类型(在很多语言中,它只能用于数值),因此可以使用字符串甚至对象。 其次,条件的值不需要是常量,也可以是变量或表达式。看下面的例子:
switch ('hello xhsRookies') {
case 'hello' + ' xhsRookies':
console.log('hello,xhs-rookies')
break
case 'goodbye':
console.log('goodbye,xhs-rookies')
break
default:
console.log('sorry,xhs-rookies')
}
这个例子在 switch
语句中使用了字符串。第一个条件实际上使用的是表达式,求值为两个字符串 拼接后的结果。因为拼接后的结果等于 switch
的参数,所以 console.log
会输出 hello,xhs-rookies
。能够在条件判断中使用表达式,就可以在判断中加入更多逻辑:
let xhsNumber = 25
switch (true) {
case xhsNumber < 0:
console.log('xhsNumber less than 0.')
break
case xhsNumber >= 0 && xhsNumber <= 10:
console.log('xhsNumber between 0 and 10.')
break
case xhsNumber > 10 && xhsNumber <= 20:
console.log('xhsNumber between 10 and 20.')
break
default:
console.log('xhsNumber more than 20.')
}
上面的代码首先在外部定义了变量 xhsNumber
,而传给 switch
语句的参数之所以是 true
,就是因为每个条件的表达式都会返回布尔值。条件的表达式分别被求值,直到有表达式返回 true
。否则,就会一 直跳到 default
语句(这个例子正是如此)。
try/catch 语句
try/catch
语句,作为在 JavaScript
中处理异常的一种方式,基本的语法如下所示:
try {
// 可能出错的代码
} catch (error) {
// 出错时要做什么
}
任何可能出错的代码都应该放到 try
块中,而处理错误的代码则放在 catch
块中,如下所示:
try {
dosome...
} catch (error) {
console.log("An error happened!");
}
如果 try
块中有代码发生错误,代码会立即退出执行,并跳到 catch
块中。catch
块此时接收到一个对象,该对象包含发生错误的相关信息。
try/catch
语句中可选的 finally
子句始终运行。如果 try
块中的代码运行完,则接着执行 finally
块中的代码。如果出错并执行 catch
块中的代码,但 finally
块中的代码仍执行。try
或 catch
块无法阻止 finally
块执行,包括 return
语句
try {
return 2
} catch (error) {
return 1
} finally {
return 0
}
在这个 try/catch
语句的各个部分都只放了一个 return
语句。看起来该函数应该返回 2
, 因为它在 try
块中,不会导致错误。但是,finally
块的存在导致 try
块中的 return
语句被忽略。 因此,无论什么情况下调用该 try/catch
都会返回 0
。如果去掉 finally
子句,该函数会返回 2
。如果写出 finally
子句,catch
块就成了可选的。
自测题目
一、下列代码输出什么?
let xhsA = 1,
xhsB = 0
while (xhsA <= 100) {
xhsB = xhsB + xhsA
xhsA++
}
console.log(xhsB)
let xhsC = 1,
xhsD = 0
do {
xhsD = xhsD + xhsC
xhsA++
} while (xhsC <= 100)
console.log(xhsD)
let xhsE = 0
for (let xhsF = 1; xhsF <= 100; xhsF++) {
xhsE += xhsF
}
console.log(xhsE)
二、下列代码输出什么?
let xhsType = 'teacher'
switch (xhsType) {
case 'student':
console.log('我是student!')
break
case 'teacher':
console.log('我是teacher!')
case 'parent':
console.log('我是parent!或者 我是teacher')
break
default:
console.log('我是person!')
}
三、下列代码输出什么?
try {
let xhsNumber1 = 10
console.log(xhsNumber1)
} catch (error) {
let xhsNumber2 = 10 / 2
console.log(xhsNumber2)
} finally {
let xhsNumber3 = 10 / 5
console.log(xhsNumber3)
}
题目解析
一、
Answer 5050 5050 5050
此题目考查的是 while
、do while
、for
循环语句,第一个 while
循环一开始定义 xhsA=1
,满足小于等于 100
,所以循环执行 xshB = xhsB + xhsA;xhsA++;
直到 xshA
不满足小于等于 100
,退出循环,此循环等价于 1+2+3+4+······+99+100=5050
;第二个 do while
循环,和 while
循环过程一样,唯一区别是,do while
循环不管什么条件,先执行一遍块内代码,然后判断 xhsC
是否满足小于等于 100
,最后输出结果 5050;for
循环一开始定义 xhsF=1
,满足小于等于 100
,所以循环执行 xhsE+=xhsF;
直到 xhsF
不满足小于等于 100
,退出循环,此循环也等价于 1+2+3+4+······+99+100=5050
二、
Answer 我是 teacher! 我是 parent!或者 我是 teacher
此题目考查的是 switch
循环语句,一开始定义 xhsType = 'teacher';
传入 switch
语句中,匹配到 case 'teacher':
执行后面的语句,输出“我是 teacher!”,由于后面没有 break;
语句跳出循环,所以继续执行后面的语句输出“我是 parent!或者 我是 teacher”,遇到 break;
跳出循环
三、
Answer 10 2
此题目考察的是 try/catch
语句,在 try
块中定义了 xhsNumber1
的值为 10
,然后打印输出,这个过程并没有错误抛出,所以 catch
块中代码不会被执行,但是 finally
中的代码,不管 try
块中有没有错误抛出,都会执行,所以输出 xhsNumber3
的值 2
。